├── .eslintrc.js
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── __mock__
├── README
├── jpngirl01.mdl
├── leet.mdl
└── ratamahatta.md2
├── const
├── constants.ts
└── structs.ts
├── dat
├── DatButton.tsx
├── DatColor.tsx
├── DatFile.tsx
├── DatFolder.tsx
├── DatInput.tsx
├── DatItem.tsx
├── DatLabelText.tsx
├── DatNumber.tsx
├── DatRange.tsx
├── DatSelect.tsx
└── DatWrapper.tsx
├── index.tsx
├── lib
├── __tests__
│ ├── __image_snapshots__
│ │ ├── texture-renderer-ts-test-textures-building-should-build-valid-backpack-texture-1-snap.png
│ │ └── texture-renderer-ts-test-textures-building-should-build-valid-skin-texture-1-snap.png
│ ├── __snapshots__
│ │ ├── binaryReader.ts.snap
│ │ ├── geometryBuilder.ts.snap
│ │ ├── geometryTransformer.ts.snap
│ │ └── modelDataParser.ts.snap
│ ├── binaryReader.ts
│ ├── geometryBuilder.ts
│ ├── geometryTransformer.ts
│ ├── modelDataParser.ts
│ └── textureRenderer.ts
├── binaryReader.ts
├── dataTypes.ts
├── geometryBuilder.ts
├── geometryTransformer.ts
├── modelController.ts
├── modelDataParser.ts
├── modelRenderer.ts
├── screneRenderer.ts
└── textureBuilder.ts
├── modules.d.ts
├── package.json
├── screenshot.png
├── template.html
├── tsconfig.json
├── ui
├── App.tsx
├── BackgroundContainer.tsx
├── Controller.tsx
├── ControllerContainer.tsx
├── Dropzone.tsx
├── FileContainer.tsx
├── GithubButton.tsx
├── GlobalStyles.tsx
├── LoadingScreen.tsx
├── Renderer.tsx
└── StartScreen.tsx
├── webpack.config.ts
└── yarn.lock
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: 'typescript-eslint-parser',
3 | extends: ['standard', 'plugin:react/recommended'],
4 | plugins: ['typescript', 'prettier', 'arca'],
5 | rules: {
6 | indent: 'off',
7 | 'indent-legacy': ['error', 2],
8 | 'max-len': ['error', 120],
9 | 'no-undef': 'off',
10 | 'space-before-function-paren': ['error', { anonymous: 'always', named: 'never', asyncArrow: 'always' }],
11 | 'comma-dangle': 'off',
12 | 'key-spacing': ['error', { align: 'value' }],
13 | 'operator-linebreak': ['error', 'before'],
14 | 'no-unused-vars': 'off',
15 | 'typescript/no-unused-vars': 'error',
16 | 'arca/import-align': 'error',
17 | 'no-multi-spaces': ['error', { exceptions: { ImportDeclaration: true } }],
18 | 'no-dupe-class-members': 'off',
19 | 'no-useless-constructor': 'off'
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dirs
2 | node_modules/
3 | dist/
4 | .vscode/
5 | coverage/
6 | *.bak/
7 |
8 | # files
9 | package-lock.json
10 | *.temp
11 | TODO.md
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "10"
4 |
5 | cache:
6 | directories:
7 | - "node_modules"
8 |
9 | script:
10 | - yarn test
11 | - yarn build
12 |
13 | deploy:
14 | provider: pages
15 | github-token: $GITHUB_TOKEN
16 | committer-from-gh: true
17 | skip-cleanup: true
18 | keep-history: true
19 | local-dir: dist
20 | repo: danakt/web-hlmv
21 | target-branch: gh-pages
22 | on:
23 | branch: master
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Danakt Frost
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Web Half-Life Model Viewer [](https://travis-ci.org/danakt/web-hlmv)
2 |
3 | This repo contains the source code powering [danakt.com/web-hlmv](https://danakt.com/web-hlmv). The tool was made as a simple multiplatform alternative to [Half-Life Model Viewer](https://github.com/ValveSoftware/halflife/tree/master/utils/mdlviewer).
4 |
5 |
6 |

7 |
8 |
9 | ## Todo
10 |
11 | - Fix bone positions calculation (resolve problems with weapons rendering)
12 | - Add first person weapons viewing and mirroring model
13 | - Add viewing textures
14 | — Add chrome textures
15 | - Make parsing and processing models in worker
16 | - Make mobile interface
17 |
18 | Create an [issue](https://github.com/danakt/web-hlmv/issues) to offer new features.
19 |
20 | ## License
21 |
22 | [MIT](LICENSE) © 2019
23 | This product was made using technologies licensed from id Software and Valve
24 | Corporation.
25 |
--------------------------------------------------------------------------------
/__mock__/README:
--------------------------------------------------------------------------------
1 | Here are some mock models :)
--------------------------------------------------------------------------------
/__mock__/jpngirl01.mdl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danakt/web-hlmv/2979a66eb3ccc5db8e59523c9c837cb2a5a502b9/__mock__/jpngirl01.mdl
--------------------------------------------------------------------------------
/__mock__/leet.mdl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danakt/web-hlmv/2979a66eb3ccc5db8e59523c9c837cb2a5a502b9/__mock__/leet.mdl
--------------------------------------------------------------------------------
/__mock__/ratamahatta.md2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danakt/web-hlmv/2979a66eb3ccc5db8e59523c9c837cb2a5a502b9/__mock__/ratamahatta.md2
--------------------------------------------------------------------------------
/const/constants.ts:
--------------------------------------------------------------------------------
1 | /** Supported model format version */
2 | export const VERSION = 10
3 |
4 | /** Maximum number of bone controllers per bone */
5 | export const MAX_PER_BONE_CONTROLLERS = 6
6 |
7 | /** Flag of texture masking */
8 | export const NF_MASKED = 0x0040
9 |
10 | /** Number of colors */
11 | export const PALETTE_ENTRIES = 256
12 |
13 | /** Number of channels for RGB color. Was "PALETTE_CHANNELS" */
14 | export const RGB_SIZE = 3
15 |
16 | /** Number of channels for RGBA color. Was "PALETTE_CHANNELS_ALPHA" */
17 | export const RGBA_SIZE = 4
18 |
19 | /** Total size of a palette, in bytes. */
20 | export const PALETTE_SIZE = PALETTE_ENTRIES * RGB_SIZE
21 |
22 | /** The index in a palette where the alpha color is stored. Used for transparent textures. */
23 | export const PALETTE_ALPHA_INDEX = 255 * RGB_SIZE
24 |
25 | /** Number of bones allowed at source movement */
26 | export const MAX_SRCBONES = 512
27 |
28 | /** Number of axles in 3d space */
29 | export const AXLES_NUM = 3
30 |
31 | /** Animation value items index constants */
32 | export const enum ANIM_VALUE {
33 | VALUE = 0,
34 | VALID,
35 | TOTAL
36 | }
37 |
38 | /** Triangle fan type */
39 | export const TRIANGLE_FAN = 0
40 |
41 | /** Triangle strip type */
42 | export const TRIANGLE_STRIP = 1
43 |
44 | /** Motion flag X */
45 | export const MOTION_X = 0x0001
46 |
47 | /** Motion flag Y */
48 | export const MOTION_Y = 0x0002
49 |
50 | /** Motion flag Z */
51 | export const MOTION_Z = 0x0004
52 |
53 | /** Controller that wraps shortest distance */
54 | export const RLOOP = 0x8000
55 |
56 | /** Default interface background color */
57 | export const INITIAL_UI_BACKGROUND = '#4d7f7e'
58 |
--------------------------------------------------------------------------------
/const/structs.ts:
--------------------------------------------------------------------------------
1 | import { StructResult, int, float, string, array, vec3, ushort } from '../lib/dataTypes'
2 | import { MAX_PER_BONE_CONTROLLERS } from './constants'
3 |
4 | /**
5 | * Head of mdl-file
6 | */
7 | export const header = {
8 | /** Model format ID */
9 | id: int,
10 | /** Format version number */
11 | version: int,
12 | /** The internal name of the model */
13 | name: string(64),
14 | /** Data size of MDL file in bytes */
15 | length: int,
16 | /** Position of player viewpoint relative to model origin */
17 | eyePosition: vec3,
18 | /** Corner of model hull box with the least X/Y/Z values */
19 | max: vec3,
20 | /** Opposite corner of model hull box */
21 | min: vec3,
22 | /** Min position of view bounding box */
23 | bbmin: vec3,
24 | /** Max position of view bounding box */
25 | bbmax: vec3,
26 | /**
27 | * Binary flags in little-endian order.
28 | * ex (00000001, 00000000, 00000000, 11000000) means flags for position
29 | * 0, 30, and 31 are set. Set model flags section for more information
30 | */
31 | flags: int,
32 |
33 | // After this point, the header contains many references to offsets
34 | // within the MDL file and the number of items at those offsets.
35 | // Offsets are from the very beginning of the file.
36 | // Note that indexes/counts are not always paired and ordered consistently.
37 |
38 | /** Number of bones */
39 | numBones: int,
40 | /** Offset of first data section */
41 | boneIndex: int,
42 | /** Number of bone controllers */
43 | numBoneControllers: int,
44 | /** Offset of bone controllers */
45 | boneControllerIndex: int,
46 | /** Number of complex bounding boxes */
47 | numHitboxes: int,
48 | /** Offset of hit boxes */
49 | hitBoxIndex: int,
50 | /** Number of sequences */
51 | numSeq: int,
52 | /** Offset of sequences */
53 | seqIndex: int,
54 | /** Number of demand loaded sequences */
55 | numSeqGroups: int,
56 | /** Offset of demand loaded sequences */
57 | seqGroupIndex: int,
58 | /** Number of raw textures */
59 | numTextures: int,
60 | /** Offset of raw textures */
61 | textureIndex: int,
62 | /** Offset of textures data */
63 | textureDataIndex: int,
64 | /** Number of replaceable textures */
65 | numSkinRef: int,
66 | numSkinFamilies: int,
67 | skinIndex: int,
68 | /** Number of body parts */
69 | numBodyParts: int,
70 | /** Index of body parts */
71 | bodyPartIndex: int,
72 | /** Number queryable attachable points */
73 | numAttachments: int,
74 | attachmentIndex: int,
75 | // This seems to be obsolete.
76 | // Probably replaced by events that reference external sounds?
77 | soundTable: int,
78 | soundIndex: int,
79 | soundGroups: int,
80 | soundGroupIndex: int,
81 | /** Animation node to animation node transition graph */
82 | numTransitions: int,
83 | transitionIndex: int
84 | }
85 |
86 | export type Header = StructResult
87 |
88 | /**
89 | * Bone description
90 | */
91 | export const bone = {
92 | /** Bone name for symbolic links */
93 | name: string(32),
94 | /** Parent bone */
95 | parent: int,
96 | /** ?? */
97 | flags: int,
98 | /** Bone controller index, -1 == none */
99 | boneController: array(MAX_PER_BONE_CONTROLLERS, int),
100 | /** Default DoF values */
101 | value: array(MAX_PER_BONE_CONTROLLERS, float),
102 | /** Scale for delta DoF values */
103 | scale: array(MAX_PER_BONE_CONTROLLERS, float)
104 | }
105 |
106 | export type Bone = StructResult
107 |
108 | /**
109 | * Bone controllers
110 | */
111 | export const boneController = {
112 | bone: int,
113 | type: int,
114 | start: float,
115 | end: float,
116 | rest: int,
117 | index: int
118 | }
119 |
120 | export type BoneController = StructResult
121 |
122 | /**
123 | * Attachment
124 | */
125 | export const attachment = {
126 | name: string(32),
127 | type: int,
128 | bone: int,
129 | /** Attachment point */
130 | org: vec3,
131 | vectors: array(3, vec3)
132 | }
133 |
134 | export type Attachment = StructResult
135 |
136 | /**
137 | * Bounding boxes
138 | */
139 | export const boundingBox = {
140 | bone: int,
141 | /** Intersection group */
142 | group: int,
143 | /** Bounding box */
144 | bbmin: vec3,
145 | bbmax: vec3
146 | }
147 |
148 | export type BoundingBox = StructResult
149 |
150 | /**
151 | * Sequence description
152 | */
153 | export const seqDesc = {
154 | /** Sequence label */
155 | label: string(32),
156 |
157 | /** Frames per second */
158 | fps: float,
159 | /** Looping/non-looping flags */
160 | flags: int,
161 |
162 | activity: int,
163 | actWeight: int,
164 |
165 | numEvents: int,
166 | eventIndex: int,
167 |
168 | /** Number of frames per sequence */
169 | numFrames: int,
170 |
171 | /** Number of foot pivots */
172 | numPivots: int,
173 | pivotIndex: int,
174 |
175 | motionType: int,
176 | motionBone: int,
177 | linearMovement: vec3,
178 | autoMovePosIndex: int,
179 | autoMoveAngleIndex: int,
180 |
181 | /** Per sequence bounding box */
182 | bbmin: vec3,
183 | bbmax: vec3,
184 |
185 | numBlends: int,
186 | /** "anim" pointer relative to start of sequence group data */
187 | animIndex: int,
188 |
189 | // [blend][bone][X, Y, Z, XR, YR, ZR]
190 | /** X, Y, Z, XR, YR, ZR */
191 | blendType: array(2, int),
192 | /** Starting value */
193 | blendStart: array(2, float),
194 | /** Ending value */
195 | blendEnd: array(2, float),
196 | blendParent: int,
197 |
198 | /** Sequence group for demand loading */
199 | seqGroup: int,
200 |
201 | /** Transition node at entry */
202 | entryNode: int,
203 | /** Transition node at exit */
204 | exitNode: int,
205 | /** Transition rules */
206 | nodeFlags: int,
207 |
208 | /** Auto advancing sequences */
209 | nextSeq: int
210 | }
211 |
212 | export type SequenceDesc = StructResult
213 |
214 | /**
215 | * Demand loaded sequence groups
216 | */
217 | export const seqGroup = {
218 | /** Textual name */
219 | label: string(32),
220 | /** File name */
221 | name: string(64),
222 | /** Was "cache" - index pointer */
223 | unused1: int,
224 | /** Was "data" - hack for group 0 */
225 | unused2: int
226 | }
227 |
228 | export type SequenceGroup = StructResult
229 |
230 | /**
231 | * Body part index
232 | */
233 | export const bodyPart = {
234 | name: string(64),
235 | numModels: int,
236 | base: int,
237 | /** Index into models array */
238 | modelIndex: int
239 | }
240 |
241 | export type BodyPart = StructResult
242 |
243 | /**
244 | * Texture info
245 | */
246 | export const texture = {
247 | /** Texture name */
248 | name: string(64),
249 | /** Flags */
250 | flags: int,
251 | /** Texture width */
252 | width: int,
253 | /** Texture height */
254 | height: int,
255 | /** Texture data offset */
256 | index: int
257 | }
258 |
259 | export type Texture = StructResult
260 |
261 | /**
262 | * Sub models
263 | */
264 | export const subModel = {
265 | name: string(64),
266 |
267 | type: int,
268 |
269 | boundingRadius: float,
270 |
271 | numMesh: int,
272 | meshIndex: int,
273 |
274 | /** Number of unique vertices */
275 | numVerts: int,
276 | /** Vertex bone info */
277 | vertInfoIndex: int,
278 | /** Vertex vec3 */
279 | vertIndex: int,
280 | /** Number of unique surface normals */
281 | numNorms: int,
282 | /** Normal bone info */
283 | normInfoIndex: int,
284 | /** Normal vec3 */
285 | normIndex: int,
286 |
287 | /** Deformation groups */
288 | numGroups: int,
289 | groupIndex: int
290 | }
291 |
292 | export type SubModel = StructResult
293 |
294 | /**
295 | * Mesh info
296 | */
297 | export const mesh = {
298 | numTris: int,
299 | triIndex: int,
300 | skinRef: int,
301 | /** Per mesh normals */
302 | numNorms: int,
303 | /** Normal vec3_t */
304 | normIndex: int
305 | }
306 |
307 | export type Mesh = StructResult
308 |
309 | /**
310 | * Animation description
311 | */
312 | export const animation = {
313 | offset: array(6, ushort)
314 | }
315 |
316 | export type Animation = StructResult
317 |
--------------------------------------------------------------------------------
/dat/DatButton.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import styled from 'styled-components'
3 | import { DatItem } from './DatItem'
4 |
5 | const DatItemButton = styled(DatItem)`
6 | border-left-color: #e61d5f;
7 | cursor: pointer;
8 |
9 | &:hover {
10 | background: #111;
11 | }
12 | `
13 |
14 | type Props = {
15 | onClick?: () => void
16 | children?: React.ReactNode
17 | }
18 |
19 | export const DatButton = (props: Props) => {props.children}
20 |
--------------------------------------------------------------------------------
/dat/DatColor.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import styled from 'styled-components'
3 | import { DatItem } from './DatItem'
4 | import { DatLabelText } from './DatLabelText'
5 | import { ChromePicker } from 'react-color'
6 |
7 | const Color = styled.div`
8 | width: 60%;
9 | text-align: center;
10 | font-weight: 700;
11 | color: #fff;
12 | text-shadow: rgba(0, 0, 0, 0.7) 0 1px 1px;
13 | vertical-align: middle;
14 | border: 3px solid #1a1a1a;
15 | cursor: pointer;
16 | `
17 |
18 | const Overlay = styled.div`
19 | position: fixed;
20 | z-index: 99;
21 | left: 0;
22 | top: 0;
23 | right: 0;
24 | bottom: 0;
25 | `
26 |
27 | const Picker = styled.div`
28 | z-index: 100;
29 | position: absolute;
30 | right: 10px;
31 | top: 35px;
32 | `
33 |
34 | type Props = {
35 | label: React.ReactNode
36 | value: string
37 | onChange: (color: string) => void
38 | }
39 |
40 | export const DatColor = (props: Props) => {
41 | const [isPickerShown, showPicker] = React.useState(false)
42 |
43 | return (
44 |
45 |
46 | {props.label}
47 |
48 | showPicker(true)}>
49 | {props.value}
50 |
51 |
52 |
53 | {isPickerShown && (
54 |
55 | showPicker(false)} />
56 |
57 |
58 | props.onChange(result.hex)} />
59 |
60 |
61 | )}
62 |
63 | )
64 | }
65 |
--------------------------------------------------------------------------------
/dat/DatFile.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import styled from 'styled-components'
3 | import { DatItem } from './DatItem'
4 | import { DatLabelText } from './DatLabelText'
5 | import { DatInput } from './DatInput'
6 |
7 | type Props = {
8 | onChange?: (value: File) => void
9 | label: React.ReactNode
10 | disabled?: boolean
11 | }
12 |
13 | const DatItemNumber = styled(DatItem)`
14 | /* border-left-color: #2fa1d6; */
15 | `
16 |
17 | const InputWrapper = styled.div`
18 | width: 60%;
19 | position: relative;
20 | overflow: hidden;
21 | display: inline-block;
22 | cursor: pointer;
23 | `
24 |
25 | const Button = styled(DatInput)`
26 | display: block;
27 | color: #fff;
28 | left: 0;
29 | top: 0;
30 | width: 100%;
31 | height: 100%;
32 | `
33 |
34 | const File = styled.input`
35 | position: absolute;
36 | font-size: 100px;
37 | left: 0;
38 | top: 0;
39 | opacity: 0;
40 | cursor: pointer;
41 | `
42 |
43 | export const DatFile = (props: Props) => (
44 |
45 | {props.label}
46 |
47 |
48 |
49 |
50 | {
54 | if (typeof props.onChange === 'function') {
55 | const files: File[] = Array.from(event.target.files || [])
56 |
57 | if (files.length) {
58 | props.onChange(files[0])
59 | }
60 | }
61 | }}
62 | disabled={props.disabled}
63 | />
64 |
65 |
66 | )
67 |
--------------------------------------------------------------------------------
/dat/DatFolder.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import styled from 'styled-components'
3 |
4 | const DatFolderTitle = styled.div`
5 | user-select: none;
6 | display: block;
7 | cursor: pointer;
8 | padding: 5px 5px 5px 16px;
9 | line-height: 22px;
10 | background: url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==)
11 | #000 6px 48% no-repeat;
12 |
13 | &.closed {
14 | background: url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==)
15 | #000 6px 48% no-repeat;
16 | }
17 | `
18 |
19 | type Props = {
20 | title: string
21 | defaultHidden?: boolean
22 | children?: React.ReactNode
23 | }
24 |
25 | export const DatFolder = (props: Props) => {
26 | const [isHidden, setHidden] = React.useState(props.defaultHidden == null ? false : props.defaultHidden)
27 |
28 | return (
29 |
30 |
setHidden(!isHidden)}>
31 | {props.title}
32 |
33 |
34 |
41 | {props.children}
42 |
43 |
44 | )
45 | }
46 |
--------------------------------------------------------------------------------
/dat/DatInput.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const DatInput = styled.input`
4 | background: #303030;
5 | border: 3px solid #1a1a1a;
6 | border-radius: 0;
7 | padding: 2px 5px;
8 | margin: 0;
9 | outline: none;
10 | font-size: inherit;
11 | color: #2fa1d6;
12 |
13 | &:hover {
14 | background-color: #3c3c3c;
15 | }
16 |
17 | &:disabled {
18 | color: #545454;
19 | }
20 | `
21 |
--------------------------------------------------------------------------------
/dat/DatItem.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const DatItem = styled.label`
4 | display: block;
5 | display: flex;
6 | align-items: center;
7 | justify-content: flex-start;
8 | width: 100%;
9 | padding: 6px 6px 6px 8px;
10 | line-height: 24px;
11 | border-left: 5px solid #dad5cb;
12 | border-bottom: 1px solid #272727;
13 | background-color: #1a1a1a;
14 | `
15 |
--------------------------------------------------------------------------------
/dat/DatLabelText.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const DatLabelText = styled.span`
4 | width: 40%;
5 | min-width: 0;
6 | white-space: nowrap;
7 | overflow: hidden;
8 | text-overflow: ellipsis;
9 | user-select: none;
10 | `
11 |
--------------------------------------------------------------------------------
/dat/DatNumber.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import styled from 'styled-components'
3 | import { DatItem } from './DatItem'
4 | import { DatLabelText } from './DatLabelText'
5 | import { DatInput } from './DatInput'
6 |
7 | type Props = {
8 | onChange?: (value: number) => void
9 | value: number
10 | label: React.ReactNode
11 | disabled?: boolean
12 | }
13 |
14 | const DatItemNumber = styled(DatItem)`
15 | border-left-color: #2fa1d6;
16 | `
17 |
18 | const NumberInput = styled(DatInput)`
19 | width: 60%;
20 | `
21 |
22 | export const DatNumber = (props: Props) => (
23 |
24 | {props.label}
25 |
26 | typeof props.onChange === 'function' && props.onChange(Number(event.target.value))}
31 | disabled={props.disabled}
32 | />
33 |
34 | )
35 |
--------------------------------------------------------------------------------
/dat/DatRange.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import styled from 'styled-components'
3 |
4 | import { DatItem } from './DatItem'
5 | import { DatLabelText } from './DatLabelText'
6 | import { DatInput } from './DatInput'
7 |
8 | const DatItemRange = styled(DatItem)`
9 | border-left-color: #2fa1d6;
10 | `
11 |
12 | const NumberInput = styled(DatInput)`
13 | width: 20%;
14 | `
15 |
16 | const RangeInput = styled.input`
17 | width: calc(40% - 6px);
18 | appearance: none;
19 | outline: none;
20 | padding: 0;
21 | cursor: pointer;
22 | overflow: hidden;
23 | height: 19px;
24 | margin: 3px;
25 | background: #2fa1d6;
26 |
27 | &::-webkit-slider-thumb {
28 | position: relative;
29 | appearance: none;
30 | height: 0;
31 | width: 0;
32 | background: #0199ff;
33 | border: 0;
34 | top: 50%;
35 | margin-top: -6.5px;
36 | box-shadow: 1000px 0 0 1000px #303030;
37 | transition: background-color 150ms;
38 | }
39 |
40 | &::-moz-range-thumb {
41 | position: relative;
42 | appearance: none;
43 | height: 0;
44 | width: 0;
45 | background: #0199ff;
46 | border: 0;
47 | top: 50%;
48 | margin-top: -6.5px;
49 | box-shadow: 1000px 0 0 1000px #303030;
50 | transition: background-color 150ms;
51 | }
52 | `
53 |
54 | type Props = {
55 | value: number
56 | label: React.ReactNode
57 | min: number
58 | max: number
59 | step?: number
60 | disabled?: boolean
61 | onChange?: (value: number) => void
62 | onChangeStart?: (value: number) => void
63 | onChangeComplete?: (value: number) => void
64 | }
65 |
66 | export const DatRange = (props: Props) => (
67 |
68 | {props.label}
69 |
70 | props.onChange && props.onChange(Number(event.target.value))}
76 | step={props.step}
77 | onMouseDown={event => props.onChangeStart && props.onChangeStart(Number((event.target as any).value))}
78 | onMouseUp={event => props.onChangeComplete && props.onChangeComplete(Number((event.target as any).value))}
79 | />
80 |
81 | typeof props.onChange === 'function' && props.onChange(Number(event.target.value))}
86 | disabled={props.disabled}
87 | />
88 |
89 | )
90 |
--------------------------------------------------------------------------------
/dat/DatSelect.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import styled from 'styled-components'
3 | import { DatItem } from './DatItem'
4 | import { DatLabelText } from './DatLabelText'
5 |
6 | type Props = {
7 | label: React.ReactNode
8 | items: string[]
9 | activeItemIndex: number
10 | onChange: (selectedIndex: number) => void
11 | }
12 |
13 | const DatItemSelect = styled(DatItem)`
14 | border-left-color: #f4d450;
15 | `
16 |
17 | export const DatSelect = (props: Props) => (
18 |
19 | {props.label}
20 |
21 |
33 |
34 | )
35 |
--------------------------------------------------------------------------------
/dat/DatWrapper.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const DatWrapper = styled.div`
4 | color: #eee;
5 | width: 280px;
6 | `
7 |
--------------------------------------------------------------------------------
/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import * as ReactDOM from 'react-dom'
3 | import { App } from './ui/App'
4 |
5 | // Render the app
6 | ReactDOM.render(, document.getElementById('root'))
7 |
--------------------------------------------------------------------------------
/lib/__tests__/__image_snapshots__/texture-renderer-ts-test-textures-building-should-build-valid-backpack-texture-1-snap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danakt/web-hlmv/2979a66eb3ccc5db8e59523c9c837cb2a5a502b9/lib/__tests__/__image_snapshots__/texture-renderer-ts-test-textures-building-should-build-valid-backpack-texture-1-snap.png
--------------------------------------------------------------------------------
/lib/__tests__/__image_snapshots__/texture-renderer-ts-test-textures-building-should-build-valid-skin-texture-1-snap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danakt/web-hlmv/2979a66eb3ccc5db8e59523c9c837cb2a5a502b9/lib/__tests__/__image_snapshots__/texture-renderer-ts-test-textures-building-should-build-valid-skin-texture-1-snap.png
--------------------------------------------------------------------------------
/lib/__tests__/__snapshots__/binaryReader.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should parse multiple structs in binary file with offset: leet header 1`] = `
4 | Object {
5 | "attachmentIndex": 6428,
6 | "bbmax": Float32Array [
7 | 0,
8 | 0,
9 | 0,
10 | ],
11 | "bbmin": Float32Array [
12 | 0,
13 | 0,
14 | 0,
15 | ],
16 | "bodyPartIndex": 2094732,
17 | "boneControllerIndex": 6404,
18 | "boneIndex": 244,
19 | "eyePosition": Float32Array [
20 | 0,
21 | 0,
22 | 0,
23 | ],
24 | "flags": 0,
25 | "hitBoxIndex": 6604,
26 | "id": 1414743113,
27 | "length": 2401992,
28 | "max": Float32Array [
29 | 0,
30 | 0,
31 | 0,
32 | ],
33 | "min": Float32Array [
34 | 0,
35 | 0,
36 | 0,
37 | ],
38 | "name": "leet\\\\leet.mdl",
39 | "numAttachments": 2,
40 | "numBodyParts": 2,
41 | "numBoneControllers": 1,
42 | "numBones": 55,
43 | "numHitboxes": 21,
44 | "numSeq": 111,
45 | "numSeqGroups": 1,
46 | "numSkinFamilies": 1,
47 | "numSkinRef": 3,
48 | "numTextures": 3,
49 | "numTransitions": 0,
50 | "seqGroupIndex": 2094628,
51 | "seqIndex": 2072508,
52 | "skinIndex": 2117056,
53 | "soundGroupIndex": 0,
54 | "soundGroups": 0,
55 | "soundIndex": 0,
56 | "soundTable": 0,
57 | "textureDataIndex": 2117064,
58 | "textureIndex": 2116816,
59 | "transitionIndex": 2094732,
60 | "version": 10,
61 | }
62 | `;
63 |
64 | exports[`should parse multiple structs in binary file: leet textures info 1`] = `
65 | Array [
66 | Object {
67 | "flags": 0,
68 | "height": 512,
69 | "index": 2117064,
70 | "name": "Arab_dmbase1.bmp",
71 | "width": 512,
72 | },
73 | Object {
74 | "flags": 3,
75 | "height": 64,
76 | "index": 2379976,
77 | "name": "Chrome3.bmp",
78 | "width": 64,
79 | },
80 | Object {
81 | "flags": 0,
82 | "height": 128,
83 | "index": 2384840,
84 | "name": "Backpack1.BMP",
85 | "width": 128,
86 | },
87 | ]
88 | `;
89 |
90 | exports[`should parse some struct in binary file with offset: some hitbox 1`] = `
91 | Object {
92 | "bbmax": Float32Array [
93 | 4,
94 | 5.559999942779541,
95 | 6.75,
96 | ],
97 | "bbmin": Float32Array [
98 | -4.690000057220459,
99 | -4.440000057220459,
100 | -6.75,
101 | ],
102 | "bone": 1,
103 | "group": 3,
104 | }
105 | `;
106 |
107 | exports[`should parse some struct in binary file: leet header 1`] = `
108 | Object {
109 | "attachmentIndex": 6428,
110 | "bbmax": Float32Array [
111 | 0,
112 | 0,
113 | 0,
114 | ],
115 | "bbmin": Float32Array [
116 | 0,
117 | 0,
118 | 0,
119 | ],
120 | "bodyPartIndex": 2094732,
121 | "boneControllerIndex": 6404,
122 | "boneIndex": 244,
123 | "eyePosition": Float32Array [
124 | 0,
125 | 0,
126 | 0,
127 | ],
128 | "flags": 0,
129 | "hitBoxIndex": 6604,
130 | "id": 1414743113,
131 | "length": 2401992,
132 | "max": Float32Array [
133 | 0,
134 | 0,
135 | 0,
136 | ],
137 | "min": Float32Array [
138 | 0,
139 | 0,
140 | 0,
141 | ],
142 | "name": "leet\\\\leet.mdl",
143 | "numAttachments": 2,
144 | "numBodyParts": 2,
145 | "numBoneControllers": 1,
146 | "numBones": 55,
147 | "numHitboxes": 21,
148 | "numSeq": 111,
149 | "numSeqGroups": 1,
150 | "numSkinFamilies": 1,
151 | "numSkinRef": 3,
152 | "numTextures": 3,
153 | "numTransitions": 0,
154 | "seqGroupIndex": 2094628,
155 | "seqIndex": 2072508,
156 | "skinIndex": 2117056,
157 | "soundGroupIndex": 0,
158 | "soundGroups": 0,
159 | "soundIndex": 0,
160 | "soundTable": 0,
161 | "textureDataIndex": 2117064,
162 | "textureIndex": 2116816,
163 | "transitionIndex": 2094732,
164 | "version": 10,
165 | }
166 | `;
167 |
--------------------------------------------------------------------------------
/lib/__tests__/__snapshots__/geometryTransformer.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should calculate rotations of a #0 sequence of #0 frame 1`] = `
4 | Array [
5 | Float32Array [
6 | 0.9529675245285034,
7 | 0.03582799434661865,
8 | -0.3009473383426666,
9 | 0,
10 | -0.03874962404370308,
11 | 0.9992419481277466,
12 | -0.003742505796253681,
13 | 0,
14 | 0.3005851209163666,
15 | 0.015228083357214928,
16 | 0.9536334276199341,
17 | 0,
18 | 0.23384900391101837,
19 | -2.2516889572143555,
20 | 38.1921501159668,
21 | 1,
22 | ],
23 | Float32Array [
24 | 0.3009141683578491,
25 | 0.01524045504629612,
26 | 0.9535294771194458,
27 | 0,
28 | 0.9528636336326599,
29 | 0.0358240120112896,
30 | -0.30127665400505066,
31 | 0,
32 | -0.03875088319182396,
33 | 0.9992418885231018,
34 | -0.00374213932082057,
35 | 0,
36 | -2.3081765174865723,
37 | -2.352119207382202,
38 | 37.69661331176758,
39 | 1,
40 | ],
41 | Float32Array [
42 | 0.49790704250335693,
43 | -0.01644931174814701,
44 | 0.8670744895935059,
45 | 0,
46 | 0.8665439486503601,
47 | 0.04920981451869011,
48 | -0.49666887521743774,
49 | 0,
50 | -0.034498751163482666,
51 | 0.998652994632721,
52 | 0.03875594958662987,
53 | 0,
54 | -1.089145541191101,
55 | -2.2992324829101562,
56 | 39.195796966552734,
57 | 1,
58 | ],
59 | Float32Array [
60 | 0.5176874995231628,
61 | -0.0576181523501873,
62 | 0.8536275029182434,
63 | 0,
64 | 0.8551156520843506,
65 | 0.06734845042228699,
66 | -0.5140441656112671,
67 | 0,
68 | -0.027872256934642792,
69 | 0.9960644245147705,
70 | 0.08413562923669815,
71 | 0,
72 | 0.9586955904960632,
73 | -2.3671419620513916,
74 | 42.76856231689453,
75 | 1,
76 | ],
77 | Float32Array [
78 | 0.5369377732276917,
79 | -0.0982375517487526,
80 | 0.8378826379776001,
81 | 0,
82 | 0.8433792591094971,
83 | 0.08632300794124603,
84 | -0.5303393006324768,
85 | 0,
86 | -0.020229360088706017,
87 | 0.9914119839668274,
88 | 0.12920159101486206,
89 | 0,
90 | 3.088038921356201,
91 | -2.6046690940856934,
92 | 46.2859992980957,
93 | 1,
94 | ],
95 | Float32Array [
96 | 0.5555397272109985,
97 | -0.138201043009758,
98 | 0.819924533367157,
99 | 0,
100 | 0.8314095735549927,
101 | 0.10603921115398407,
102 | -0.5454482436180115,
103 | 0,
104 | -0.011562676168978214,
105 | 0.9847111701965332,
106 | 0.1738106608390808,
107 | 0,
108 | 5.296704292297363,
109 | -3.0095534324645996,
110 | 49.738643646240234,
111 | 1,
112 | ],
113 | Float32Array [
114 | 0.7382032871246338,
115 | -0.0669337585568428,
116 | 0.671249508857727,
117 | 0,
118 | 0.6725585460662842,
119 | 0.14997108280658722,
120 | -0.724688708782196,
121 | 0,
122 | -0.052161913365125656,
123 | 0.986422061920166,
124 | 0.15572600066661835,
125 | 0,
126 | 7.581770420074463,
127 | -3.5791139602661133,
128 | 53.11745834350586,
129 | 1,
130 | ],
131 | Float32Array [
132 | 0.468267023563385,
133 | 0.005843371152877808,
134 | 0.883567750453949,
135 | 0,
136 | 0.8835021257400513,
137 | 0.01075994037091732,
138 | -0.46830350160598755,
139 | 0,
140 | -0.01224365085363388,
141 | 0.9999249577522278,
142 | -0.00012415311357472092,
143 | 0,
144 | 10.862051010131836,
145 | -3.8765408992767334,
146 | 56.100223541259766,
147 | 1,
148 | ],
149 | Float32Array [
150 | -0.012243528850376606,
151 | 0.9999249577522278,
152 | -0.00012394029181450605,
153 | 0,
154 | -0.9539998769760132,
155 | -0.011644038371741772,
156 | 0.2995806932449341,
157 | 0,
158 | 0.29955679178237915,
159 | 0.003786086570471525,
160 | 0.954071044921875,
161 | 0,
162 | 11.594308853149414,
163 | -3.864509344100952,
164 | 57.32668685913086,
165 | 1,
166 | ],
167 | Float32Array [
168 | -0.2070450484752655,
169 | 0.9738600254058838,
170 | 0.09342902153730392,
171 | 0,
172 | -0.824213981628418,
173 | -0.22508104145526886,
174 | 0.5196247100830078,
175 | 0,
176 | 0.5270708203315735,
177 | 0.03058011084794998,
178 | 0.8492711186408997,
179 | 0,
180 | 7.493768215179443,
181 | -1.8693900108337402,
182 | 53.38473129272461,
183 | 1,
184 | ],
185 | Float32Array [
186 | -0.24342359602451324,
187 | 0.9228060841560364,
188 | -0.2986200749874115,
189 | 0,
190 | -0.9661379456520081,
191 | -0.2035333514213562,
192 | 0.15859250724315643,
193 | 0,
194 | 0.08557099103927612,
195 | 0.32711324095726013,
196 | 0.941102921962738,
197 | 0,
198 | 6.17183256149292,
199 | 4.3484883308410645,
200 | 53.98125457763672,
201 | 1,
202 | ],
203 | Float32Array [
204 | 0.77527916431427,
205 | 0.5714207291603088,
206 | -0.26911062002182007,
207 | 0,
208 | -0.6257953643798828,
209 | 0.7526455521583557,
210 | -0.20470714569091797,
211 | 0,
212 | 0.08557099103927612,
213 | 0.32711324095726013,
214 | 0.941102921962738,
215 | 0,
216 | 3.6785218715667725,
217 | 13.800497055053711,
218 | 50.92258071899414,
219 | 1,
220 | ],
221 | Float32Array [
222 | 0.8010517358779907,
223 | 0.5825763940811157,
224 | 0.1375526785850525,
225 | 0,
226 | 0.3332682251930237,
227 | -0.24316738545894623,
228 | -0.9109347462654114,
229 | 0,
230 | -0.4972407817840576,
231 | 0.7755478024482727,
232 | -0.38894397020339966,
233 | 0,
234 | 12.497757911682129,
235 | 20.300729751586914,
236 | 47.86130142211914,
237 | 1,
238 | ],
239 | Float32Array [
240 | 0.9729205369949341,
241 | 0.0752267837524414,
242 | -0.21855543553829193,
243 | 0,
244 | -0.15499110519886017,
245 | 0.9138008952140808,
246 | -0.3754274249076843,
247 | 0,
248 | 0.171473890542984,
249 | 0.3991350829601288,
250 | 0.9007152318954468,
251 | 0,
252 | 13.590639114379883,
253 | 19.937564849853516,
254 | 48.332359313964844,
255 | 1,
256 | ],
257 | Float32Array [
258 | 0.972916305065155,
259 | 0.07525169104337692,
260 | -0.2185656726360321,
261 | 0,
262 | -0.15501762926578522,
263 | 0.9137988686561584,
264 | -0.3754214644432068,
265 | 0,
266 | 0.171473890542984,
267 | 0.3991350829601288,
268 | 0.9007152318954468,
269 | 0,
270 | 15.669281005859375,
271 | 20.09828758239746,
272 | 47.86541748046875,
273 | 1,
274 | ],
275 | Float32Array [
276 | 0.850000262260437,
277 | 0.5253564715385437,
278 | -0.03872976824641228,
279 | 0,
280 | 0.17468616366386414,
281 | -0.35046684741973877,
282 | -0.9201402068138123,
283 | 0,
284 | -0.4969751536846161,
285 | 0.7753538489341736,
286 | -0.3896695375442505,
287 | 0,
288 | 14.708806037902832,
289 | 22.314584732055664,
290 | 49.105308532714844,
291 | 1,
292 | ],
293 | Float32Array [
294 | 0.8518550395965576,
295 | 0.35031595826148987,
296 | -0.38938626646995544,
297 | 0,
298 | -0.1654045581817627,
299 | -0.5254570841789246,
300 | -0.8345875144004822,
301 | 0,
302 | -0.4969751536846161,
303 | 0.7753538489341736,
304 | -0.3896695375442505,
305 | 0,
306 | 16.418285369873047,
307 | 23.371156692504883,
308 | 49.02741622924805,
309 | 1,
310 | ],
311 | Float32Array [
312 | 0.8677555322647095,
313 | 0.446121484041214,
314 | -0.21903404593467712,
315 | 0,
316 | 0.004011142998933792,
317 | -0.4469922184944153,
318 | -0.8945289850234985,
319 | 0,
320 | -0.4969751536846161,
321 | 0.7753538489341736,
322 | -0.3896695375442505,
323 | 0,
324 | 16.18285369873047,
325 | 23.225643157958984,
326 | 49.038143157958984,
327 | 1,
328 | ],
329 | Float32Array [
330 | 0.77527916431427,
331 | 0.5714207291603088,
332 | -0.26911062002182007,
333 | 0,
334 | -0.0052835457026958466,
335 | -0.42018282413482666,
336 | -0.9074242115020752,
337 | 0,
338 | -0.6315966248512268,
339 | 0.7049289345741272,
340 | -0.32273995876312256,
341 | 0,
342 | 8.33019733428955,
343 | 17.229021072387695,
344 | 49.307918548583984,
345 | 1,
346 | ],
347 | Float32Array [
348 | 0.8138710856437683,
349 | 0.5788562893867493,
350 | -0.05039023607969284,
351 | 0,
352 | 0.1382748782634735,
353 | -0.2771839499473572,
354 | -0.9508151412010193,
355 | 0,
356 | -0.5643526911735535,
357 | 0.7668732404708862,
358 | -0.30563339591026306,
359 | 0,
360 | 12.497757911682129,
361 | 20.300729751586914,
362 | 47.86130142211914,
363 | 1,
364 | ],
365 | Float32Array [
366 | 0.9449884295463562,
367 | -0.3259565830230713,
368 | 0.02737336978316307,
369 | 0,
370 | 0.31571274995803833,
371 | 0.8869888782501221,
372 | -0.3370107412338257,
373 | 0,
374 | 0.08557099103927612,
375 | 0.32711324095726013,
376 | 0.941102921962738,
377 | 0,
378 | 3.6134190559387207,
379 | 14.0472993850708,
380 | 50.84272003173828,
381 | 1,
382 | ],
383 | Float32Array [
384 | -0.24342359602451324,
385 | 0.9228060841560364,
386 | -0.2986200749874115,
387 | 0,
388 | -0.934245765209198,
389 | -0.1403481513261795,
390 | 0.32785242795944214,
391 | 0,
392 | 0.2606334090232849,
393 | 0.35879144072532654,
394 | 0.8962918519973755,
395 | 0,
396 | 4.83300256729126,
397 | 9.423921585083008,
398 | 52.338844299316406,
399 | 1,
400 | ],
401 | Float32Array [
402 | -0.22377370297908783,
403 | 0.9361706972122192,
404 | -0.2711268663406372,
405 | 0,
406 | -0.9579102396965027,
407 | -0.15992878377437592,
408 | 0.23839186131954193,
409 | 0,
410 | 0.17981451749801636,
411 | 0.3130609095096588,
412 | 0.9325554966926575,
413 | 0,
414 | 6.1373138427734375,
415 | 4.510844707489014,
416 | 53.996829986572266,
417 | 1,
418 | ],
419 | Float32Array [
420 | -0.23258234560489655,
421 | 0.9686709642410278,
422 | -0.08707474172115326,
423 | 0,
424 | -0.902909517288208,
425 | -0.18178017437458038,
426 | 0.38950011134147644,
427 | 0,
428 | 0.36146900057792664,
429 | 0.16921135783195496,
430 | 0.9169012904167175,
431 | 0,
432 | 6.1373138427734375,
433 | 4.510844707489014,
434 | 53.996829986572266,
435 | 1,
436 | ],
437 | Float32Array [
438 | 0.10122936964035034,
439 | -0.8921828866004944,
440 | -0.44018426537513733,
441 | 0,
442 | -0.8061943054199219,
443 | -0.332817018032074,
444 | 0.4891660511493683,
445 | 0,
446 | -0.5829264521598816,
447 | 0.30535614490509033,
448 | -0.7529638409614563,
449 | 0,
450 | 7.6745381355285645,
451 | -5.287776947021484,
452 | 52.845062255859375,
453 | 1,
454 | ],
455 | Float32Array [
456 | 0.6353446841239929,
457 | -0.34507086873054504,
458 | -0.6908423900604248,
459 | 0,
460 | -0.7652745246887207,
461 | -0.40113621950149536,
462 | -0.5034328699111938,
463 | 0,
464 | -0.10340197384357452,
465 | 0.8485373854637146,
466 | -0.5189338326454163,
467 | 0,
468 | 8.320865631103516,
469 | -10.984166145324707,
470 | 50.034584045410156,
471 | 1,
472 | ],
473 | Float32Array [
474 | 0.8428821563720703,
475 | 0.35174891352653503,
476 | 0.4072129428386688,
477 | 0,
478 | 0.5280697345733643,
479 | -0.39529338479042053,
480 | -0.7515886425971985,
481 | 0,
482 | -0.10340197384357452,
483 | 0.8485373854637146,
484 | -0.5189338326454163,
485 | 0,
486 | 14.828497886657715,
487 | -14.518614768981934,
488 | 42.95850372314453,
489 | 1,
490 | ],
491 | Float32Array [
492 | 0.9942601919174194,
493 | -0.0891028419137001,
494 | -0.059222616255283356,
495 | 0,
496 | 0.09696772694587708,
497 | 0.9843878746032715,
498 | 0.14689351618289948,
499 | 0,
500 | 0.04520948976278305,
501 | -0.15179306268692017,
502 | 0.9873780012130737,
503 | 0,
504 | 24.41675567626953,
505 | -10.517274856567383,
506 | 47.590782165527344,
507 | 1,
508 | ],
509 | Float32Array [
510 | 0.8319627642631531,
511 | 0.39122551679611206,
512 | -0.3934214413166046,
513 | 0,
514 | -0.3702550232410431,
515 | -0.1366182267665863,
516 | -0.918829083442688,
517 | 0,
518 | -0.4132179319858551,
519 | 0.9100977182388306,
520 | 0.031191887333989143,
521 | 0,
522 | 25.18910789489746,
523 | -10.712190628051758,
524 | 48.546634674072266,
525 | 1,
526 | ],
527 | Float32Array [
528 | 0.8319566249847412,
529 | 0.3912232518196106,
530 | -0.3934367597103119,
531 | 0,
532 | -0.37026888132095337,
533 | -0.13662473857402802,
534 | -0.9188225269317627,
535 | 0,
536 | -0.4132179319858551,
537 | 0.9100977182388306,
538 | 0.031191887333989143,
539 | 0,
540 | 26.96659278869629,
541 | -9.876338958740234,
542 | 47.706092834472656,
543 | 1,
544 | ],
545 | Float32Array [
546 | 0.994502067565918,
547 | 0.10034099221229553,
548 | -0.029955878853797913,
549 | 0,
550 | -0.09449213743209839,
551 | 0.9831845760345459,
552 | 0.1562669575214386,
553 | 0,
554 | 0.04513223469257355,
555 | -0.1525772213935852,
556 | 0.9872606992721558,
557 | 0,
558 | 27.42943000793457,
559 | -11.670334815979004,
560 | 47.297279357910156,
561 | 1,
562 | ],
563 | Float32Array [
564 | -0.6084877848625183,
565 | 0.7795833945274353,
566 | 0.1482982635498047,
567 | 0,
568 | -0.7922787666320801,
569 | -0.6074289679527283,
570 | -0.05765710398554802,
571 | 0,
572 | 0.04513223469257355,
573 | -0.1525772213935852,
574 | 0.9872606992721558,
575 | 0,
576 | 29.429523468017578,
577 | -11.468533515930176,
578 | 47.23703384399414,
579 | 1,
580 | ],
581 | Float32Array [
582 | 0.3981221914291382,
583 | 0.9091426730155945,
584 | 0.12230443954467773,
585 | 0,
586 | -0.9162214994430542,
587 | 0.38753047585487366,
588 | 0.10177592933177948,
589 | 0,
590 | 0.04513223469257355,
591 | -0.1525772213935852,
592 | 0.9872606992721558,
593 | 0,
594 | 29.154067993164062,
595 | -11.49632740020752,
596 | 47.245330810546875,
597 | 1,
598 | ],
599 | Float32Array [
600 | 0.9729093313217163,
601 | 0.08921302855014801,
602 | 0.21328020095825195,
603 | 0,
604 | -0.049942128360271454,
605 | 0.9818646907806396,
606 | -0.18288634717464447,
607 | 0,
608 | -0.22572803497314453,
609 | 0.1672801375389099,
610 | 0.959721028804779,
611 | 0,
612 | 24.411540985107422,
613 | -10.377358436584473,
614 | 47.514617919921875,
615 | 1,
616 | ],
617 | Float32Array [
618 | 0.8428821563720703,
619 | 0.35174891352653503,
620 | 0.4072129428386688,
621 | 0,
622 | -0.2747473418712616,
623 | 0.9320096969604492,
624 | -0.23637232184410095,
625 | 0,
626 | -0.4626699984073639,
627 | 0.08735331892967224,
628 | 0.8822165131568909,
629 | 0,
630 | 19.885791778564453,
631 | -12.40811824798584,
632 | 45.40177917480469,
633 | 1,
634 | ],
635 | Float32Array [
636 | 0.1575912982225418,
637 | 0.5291167497634888,
638 | 0.8337869644165039,
639 | 0,
640 | 0.9820759296417236,
641 | 0.004435714799910784,
642 | -0.18843376636505127,
643 | 0,
644 | -0.10340197384357452,
645 | 0.8485373854637146,
646 | -0.5189338326454163,
647 | 0,
648 | 14.998420715332031,
649 | -14.610906600952148,
650 | 42.77374267578125,
651 | 1,
652 | ],
653 | Float32Array [
654 | 0.6353446841239929,
655 | -0.34507086873054504,
656 | -0.6908423900604248,
657 | 0,
658 | -0.7174565196037292,
659 | -0.5946808457374573,
660 | -0.3627820611000061,
661 | 0,
662 | -0.2856452763080597,
663 | 0.7261409759521484,
664 | -0.6254007816314697,
665 | 0,
666 | 11.815260887145996,
667 | -12.882054328918457,
668 | 46.23495101928711,
669 | 1,
670 | ],
671 | Float32Array [
672 | 0.2560773491859436,
673 | -0.7488679885864258,
674 | -0.6112455129623413,
675 | 0,
676 | -0.8774188756942749,
677 | -0.4454282224178314,
678 | 0.1781279295682907,
679 | 0,
680 | -0.40566039085388184,
681 | 0.49070385098457336,
682 | -0.7711352109909058,
683 | 0,
684 | 8.337740898132324,
685 | -11.132905006408691,
686 | 49.96119689941406,
687 | 1,
688 | ],
689 | Float32Array [
690 | 0.2560773491859436,
691 | -0.7488679885864258,
692 | -0.6112455129623413,
693 | 0,
694 | -0.8774188756942749,
695 | -0.4454282224178314,
696 | 0.1781279295682907,
697 | 0,
698 | -0.40566039085388184,
699 | 0.49070385098457336,
700 | -0.7711352109909058,
701 | 0,
702 | 8.337740898132324,
703 | -11.132905006408691,
704 | 49.96119689941406,
705 | 1,
706 | ],
707 | Float32Array [
708 | 0.5555397272109985,
709 | -0.138201043009758,
710 | 0.819924533367157,
711 | 0,
712 | 0.8314095735549927,
713 | 0.10603921115398407,
714 | -0.5454482436180115,
715 | 0,
716 | -0.011562676168978214,
717 | 0.9847111701965332,
718 | 0.1738106608390808,
719 | 0,
720 | 7.641833782196045,
721 | -3.5939502716064453,
722 | 53.205509185791016,
723 | 1,
724 | ],
725 | Float32Array [
726 | 0.005052473861724138,
727 | -0.03283005952835083,
728 | -0.9994481801986694,
729 | 0,
730 | 0.9983357787132263,
731 | 0.057583004236221313,
732 | 0.003155333688482642,
733 | 0,
734 | 0.05744767561554909,
735 | -0.9978007078170776,
736 | 0.03306637331843376,
737 | 0,
738 | -2.1642754077911377,
739 | -6.062882900238037,
740 | 37.71051788330078,
741 | 1,
742 | ],
743 | Float32Array [
744 | -0.07205827534198761,
745 | 0.06293108314275742,
746 | -0.9954131841659546,
747 | 0,
748 | 0.9940438270568848,
749 | 0.08634017407894135,
750 | -0.06650066375732422,
751 | 0,
752 | 0.08175922185182571,
753 | -0.9942761659622192,
754 | -0.06877776980400085,
755 | 0,
756 | -2.452077627182007,
757 | 1.3586444854736328,
758 | 37.682708740234375,
759 | 1,
760 | ],
761 | Float32Array [
762 | 0.10811438411474228,
763 | 0.13742883503437042,
764 | -0.9845936298370361,
765 | 0,
766 | 0.9877378344535828,
767 | 0.0973564013838768,
768 | 0.12204853445291519,
769 | 0,
770 | 0.11262952536344528,
771 | -0.9857155084609985,
772 | -0.12521801888942719,
773 | 0,
774 | -2.448413848876953,
775 | 1.2642183303833008,
776 | 37.68306350708008,
777 | 1,
778 | ],
779 | Float32Array [
780 | -0.9795001745223999,
781 | -0.1313239336013794,
782 | 0.15275314450263977,
783 | 0,
784 | -0.16701525449752808,
785 | 0.10544656217098236,
786 | -0.9802994132041931,
787 | 0,
788 | 0.11262952536344528,
789 | -0.9857155084609985,
790 | -0.12521801888942719,
791 | 0,
792 | -0.6565347909927368,
793 | 3.5419528484344482,
794 | 21.36448860168457,
795 | 1,
796 | ],
797 | Float32Array [
798 | -0.9797776937484741,
799 | -0.0372396856546402,
800 | 0.19659338891506195,
801 | 0,
802 | -0.1954997479915619,
803 | -0.031064577400684357,
804 | -0.9802116751670837,
805 | 0,
806 | 0.04260990023612976,
807 | -0.9988234043121338,
808 | 0.023156043142080307,
809 | 0,
810 | -15.474588394165039,
811 | 1.5552599430084229,
812 | 23.675365447998047,
813 | 1,
814 | ],
815 | Float32Array [
816 | -0.19550010561943054,
817 | -0.031064588576555252,
818 | -0.9802115559577942,
819 | 0,
820 | 0.9797775745391846,
821 | 0.037239670753479004,
822 | -0.1965937465429306,
823 | 0,
824 | 0.04260990023612976,
825 | -0.9988234043121338,
826 | 0.023156043142080307,
827 | 0,
828 | -21.946643829345703,
829 | 1.2089483737945557,
830 | 20.646747589111328,
831 | 1,
832 | ],
833 | Float32Array [
834 | -0.9807296991348267,
835 | -0.07965356856584549,
836 | 0.17839471995830536,
837 | 0,
838 | -0.1817169338464737,
839 | 0.03655651956796646,
840 | -0.9826711416244507,
841 | 0,
842 | 0.07175181061029434,
843 | -0.9961520433425903,
844 | -0.050326455384492874,
845 | 0,
846 | -16.04345703125,
847 | 1.4789916276931763,
848 | 23.764080047607422,
849 | 1,
850 | ],
851 | Float32Array [
852 | -0.8388076424598694,
853 | -0.16187582910060883,
854 | 0.5198057293891907,
855 | 0,
856 | -0.532650351524353,
857 | 0.046488381922245026,
858 | -0.8450576066970825,
859 | 0,
860 | 0.11262952536344528,
861 | -0.9857155084609985,
862 | -0.12521801888942719,
863 | 0,
864 | -0.5877442359924316,
865 | 3.629394292831421,
866 | 20.73801612854004,
867 | 1,
868 | ],
869 | Float32Array [
870 | 0.16105537116527557,
871 | -0.043108075857162476,
872 | -0.986003577709198,
873 | 0,
874 | 0.9850189089775085,
875 | 0.06941179186105728,
876 | 0.15785984694957733,
877 | 0,
878 | 0.061635278165340424,
879 | -0.9966562390327454,
880 | 0.0536414310336113,
881 | 0,
882 | -2.167935371398926,
883 | -5.968456745147705,
884 | 37.71016311645508,
885 | 1,
886 | ],
887 | Float32Array [
888 | -0.4973541498184204,
889 | -0.0772644430398941,
890 | -0.8641002178192139,
891 | 0,
892 | 0.8653554320335388,
893 | 0.026580236852169037,
894 | -0.5004534125328064,
895 | 0,
896 | 0.061635278165340424,
897 | -0.9966562390327454,
898 | 0.0536414310336113,
899 | 0,
900 | 0.501383364200592,
901 | -6.682926654815674,
902 | 21.36821937561035,
903 | 1,
904 | ],
905 | Float32Array [
906 | -0.9186765551567078,
907 | -0.06541958451271057,
908 | -0.38955575227737427,
909 | 0,
910 | 0.35339945554733276,
911 | 0.3044632077217102,
912 | -0.8845401406288147,
913 | 0,
914 | 0.17647169530391693,
915 | -0.9502749443054199,
916 | -0.25658395886421204,
917 | 0,
918 | -7.022678852081299,
919 | -7.851797103881836,
920 | 8.2959566116333,
921 | 1,
922 | ],
923 | Float32Array [
924 | 0.3533990979194641,
925 | 0.3044631779193878,
926 | -0.8845402598381042,
927 | 0,
928 | 0.9186766147613525,
929 | 0.06541969627141953,
930 | 0.3895553946495056,
931 | 0,
932 | 0.17647169530391693,
933 | -0.9502749443054199,
934 | -0.25658395886421204,
935 | 0,
936 | -10.812943458557129,
937 | -6.936161994934082,
938 | 2.2980005741119385,
939 | 1,
940 | ],
941 | Float32Array [
942 | -0.8239564299583435,
943 | -0.04409169778227806,
944 | -0.5649352669715881,
945 | 0,
946 | 0.5435478091239929,
947 | 0.22028905153274536,
948 | -0.8099559545516968,
949 | 0,
950 | 0.16016140580177307,
951 | -0.9744375944137573,
952 | -0.1575426310300827,
953 | 0,
954 | -7.311529159545898,
955 | -7.896669864654541,
956 | 7.7941107749938965,
957 | 1,
958 | ],
959 | Float32Array [
960 | -0.9980331063270569,
961 | -0.06215853616595268,
962 | -0.008140950463712215,
963 | 0,
964 | 0.011448036879301071,
965 | -0.053034134209156036,
966 | -0.9985271692276001,
967 | 0,
968 | 0.061635278165340424,
969 | -0.9966562390327454,
970 | 0.0536414310336113,
971 | 0,
972 | 0.603858470916748,
973 | -6.710354804992676,
974 | 20.74085235595703,
975 | 1,
976 | ],
977 | Float32Array [
978 | 0.020229563117027283,
979 | -0.9914119839668274,
980 | -0.1292012631893158,
981 | 0,
982 | 0.7885388731956482,
983 | 0.09526759386062622,
984 | -0.6075611114501953,
985 | 0,
986 | 0.6146520376205444,
987 | -0.08958956599235535,
988 | 0.7836942076683044,
989 | 0,
990 | -1.8925654888153076,
991 | -3.585514545440674,
992 | 51.22256088256836,
993 | 1,
994 | ],
995 | ]
996 | `;
997 |
998 | exports[`should calculate rotations of a #2 sequence of #23 frame 1`] = `
999 | Array [
1000 | Float32Array [
1001 | 0.613254725933075,
1002 | -0.6249901056289673,
1003 | -0.4830176532268524,
1004 | 0,
1005 | 0.6560549736022949,
1006 | 0.7435747981071472,
1007 | -0.1291838139295578,
1008 | 0,
1009 | 0.4398983418941498,
1010 | -0.23766353726387024,
1011 | 0.8660286068916321,
1012 | 0,
1013 | 0.23384900391101837,
1014 | -2.2516889572143555,
1015 | 38.1921501159668,
1016 | 1,
1017 | ],
1018 | Float32Array [
1019 | 0.44011008739471436,
1020 | -0.23787935078144073,
1021 | 0.8658617734909058,
1022 | 0,
1023 | 0.6131036281585693,
1024 | -0.6249070167541504,
1025 | -0.4833168685436249,
1026 | 0,
1027 | 0.6560541391372681,
1028 | 0.7435756325721741,
1029 | -0.12918320298194885,
1030 | 0,
1031 | -1.7071661949157715,
1032 | -0.5343531370162964,
1033 | 38.21964645385742,
1034 | 1,
1035 | ],
1036 | Float32Array [
1037 | 0.6168481111526489,
1038 | -0.08123808354139328,
1039 | 0.782878577709198,
1040 | 0,
1041 | 0.6094624996185303,
1042 | -0.580100953578949,
1043 | -0.5404057502746582,
1044 | 0,
1045 | 0.49805009365081787,
1046 | 0.8104833960533142,
1047 | -0.30832260847091675,
1048 | 0,
1049 | -0.4798211455345154,
1050 | -1.4066646099090576,
1051 | 39.43170166015625,
1052 | 1,
1053 | ],
1054 | Float32Array [
1055 | 0.7997841835021973,
1056 | -0.3335686922073364,
1057 | 0.49907639622688293,
1058 | 0,
1059 | 0.3143659234046936,
1060 | -0.47553005814552307,
1061 | -0.8216114044189453,
1062 | 0,
1063 | 0.5113896131515503,
1064 | 0.8140043616294861,
1065 | -0.27545878291130066,
1066 | 0,
1067 | 2.058734178543091,
1068 | -1.7393500804901123,
1069 | 42.657840728759766,
1070 | 1,
1071 | ],
1072 | Float32Array [
1073 | 0.7932196855545044,
1074 | -0.45787227153778076,
1075 | 0.4014419615268707,
1076 | 0,
1077 | 0.22111776471138,
1078 | -0.39767226576805115,
1079 | -0.8904851675033569,
1080 | 0,
1081 | 0.5673707127571106,
1082 | 0.7951161861419678,
1083 | -0.21419775485992432,
1084 | 0,
1085 | 5.3516950607299805,
1086 | -3.111626625061035,
1087 | 44.716033935546875,
1088 | 1,
1089 | ],
1090 | Float32Array [
1091 | 0.6748504638671875,
1092 | -0.3734116554260254,
1093 | 0.6365066170692444,
1094 | 0,
1095 | 0.45284146070480347,
1096 | -0.47148996591567993,
1097 | -0.75672447681427,
1098 | 0,
1099 | 0.582676112651825,
1100 | 0.7989123463630676,
1101 | -0.14908893406391144,
1102 | 0,
1103 | 8.617924690246582,
1104 | -4.996114730834961,
1105 | 46.37233352661133,
1106 | 1,
1107 | ],
1108 | Float32Array [
1109 | 0.7381669282913208,
1110 | -0.5263992547988892,
1111 | 0.42191633582115173,
1112 | 0,
1113 | 0.24144895374774933,
1114 | -0.3778383731842041,
1115 | -0.8938348293304443,
1116 | 0,
1117 | 0.6299300789833069,
1118 | 0.7616704702377319,
1119 | -0.15180924534797668,
1120 | 0,
1121 | 11.395715713500977,
1122 | -6.532354354858398,
1123 | 48.996482849121094,
1124 | 1,
1125 | ],
1126 | Float32Array [
1127 | 0.3214617073535919,
1128 | -0.11787625402212143,
1129 | 0.9395571351051331,
1130 | 0,
1131 | 0.9458358883857727,
1132 | 0.08749251812696457,
1133 | -0.312633216381073,
1134 | 0,
1135 | -0.04535224661231041,
1136 | 0.9891663789749146,
1137 | 0.13961713016033173,
1138 | 0,
1139 | 14.675834655761719,
1140 | -8.87146282196045,
1141 | 50.87131118774414,
1142 | 1,
1143 | ],
1144 | Float32Array [
1145 | -0.045352160930633545,
1146 | 0.9891663789749146,
1147 | 0.13961735367774963,
1148 | 0,
1149 | -0.9885589480400085,
1150 | -0.06456500291824341,
1151 | 0.13631770014762878,
1152 | 0,
1153 | 0.1438552588224411,
1154 | -0.13183768093585968,
1155 | 0.9807775020599365,
1156 | 0,
1157 | 15.203093528747559,
1158 | -9.030384063720703,
1159 | 52.18937683105469,
1160 | 1,
1161 | ],
1162 | Float32Array [
1163 | 0.403098464012146,
1164 | 0.9135116934776306,
1165 | -0.05484294891357422,
1166 | 0,
1167 | -0.5645853281021118,
1168 | 0.2953997254371643,
1169 | 0.7707025408744812,
1170 | 0,
1171 | 0.720246434211731,
1172 | -0.2797054648399353,
1173 | 0.6348305940628052,
1174 | 0,
1175 | 12.488064765930176,
1176 | -5.213929176330566,
1177 | 48.730281829833984,
1178 | 1,
1179 | ],
1180 | Float32Array [
1181 | 0.42170655727386475,
1182 | -0.2869262099266052,
1183 | -0.8601378202438354,
1184 | 0,
1185 | -0.6891820430755615,
1186 | 0.5150174498558044,
1187 | -0.5096911787986755,
1188 | 0,
1189 | 0.5892296433448792,
1190 | 0.807731568813324,
1191 | 0.019441913813352585,
1192 | 0,
1193 | 15.061758995056152,
1194 | 0.6186381578445435,
1195 | 48.380123138427734,
1196 | 1,
1197 | ],
1198 | Float32Array [
1199 | 0.7270628809928894,
1200 | -0.5405710339546204,
1201 | 0.4232758581638336,
1202 | 0,
1203 | 0.3524029850959778,
1204 | -0.23527127504348755,
1205 | -0.9057922959327698,
1206 | 0,
1207 | 0.5892296433448792,
1208 | 0.807731568813324,
1209 | 0.019441913813352585,
1210 | 0,
1211 | 19.38116455078125,
1212 | -2.3202574253082275,
1213 | 39.570003509521484,
1214 | 1,
1215 | ],
1216 | Float32Array [
1217 | -0.8983715176582336,
1218 | 0.4073803722858429,
1219 | -0.16422487795352936,
1220 | 0,
1221 | 0.4265625476837158,
1222 | 0.8983415365219116,
1223 | -0.10500819236040115,
1224 | 0,
1225 | 0.10475174337625504,
1226 | -0.16438865661621094,
1227 | -0.9808178544044495,
1228 | 0,
1229 | 27.65191650390625,
1230 | -8.469552993774414,
1231 | 44.385005950927734,
1232 | 1,
1233 | ],
1234 | Float32Array [
1235 | -0.6951392292976379,
1236 | 0.718463659286499,
1237 | -0.02431686408817768,
1238 | 0,
1239 | 0.3466412425041199,
1240 | 0.3053700029850006,
1241 | -0.8868985772132874,
1242 | 0,
1243 | -0.6297786831855774,
1244 | -0.6249472498893738,
1245 | -0.461324006319046,
1246 | 0,
1247 | 26.90166473388672,
1248 | -7.985904693603516,
1249 | 45.2518310546875,
1250 | 1,
1251 | ],
1252 | Float32Array [
1253 | -0.7201833724975586,
1254 | 0.6923023462295532,
1255 | 0.0453125424683094,
1256 | 0,
1257 | 0.2910577058792114,
1258 | 0.36077457666397095,
1259 | -0.8860740065574646,
1260 | 0,
1261 | -0.6297786831855774,
1262 | -0.6249472498893738,
1263 | -0.461324006319046,
1264 | 0,
1265 | 25.416501998901367,
1266 | -6.450909614562988,
1267 | 45.19987869262695,
1268 | 1,
1269 | ],
1270 | Float32Array [
1271 | -0.5719186067581177,
1272 | 0.7969755530357361,
1273 | -0.19426514208316803,
1274 | 0,
1275 | 0.6013029217720032,
1276 | 0.568388044834137,
1277 | 0.5615780353546143,
1278 | 0,
1279 | 0.5579819083213806,
1280 | 0.2043645679950714,
1281 | -0.804295539855957,
1282 | 0,
1283 | 24.47283935546875,
1284 | -7.992801666259766,
1285 | 43.987422943115234,
1286 | 1,
1287 | ],
1288 | Float32Array [
1289 | 0.44248953461647034,
1290 | 0.7466558218002319,
1291 | 0.49669721722602844,
1292 | 0,
1293 | 0.7020392417907715,
1294 | -0.6330403089523315,
1295 | 0.3261912167072296,
1296 | 0,
1297 | 0.5579819083213806,
1298 | 0.2043645679950714,
1299 | -0.804295539855957,
1300 | 0,
1301 | 23.32262420654297,
1302 | -6.389963626861572,
1303 | 43.59672546386719,
1304 | 1,
1305 | ],
1306 | Float32Array [
1307 | -0.0825764611363411,
1308 | 0.9780653715133667,
1309 | 0.19123059511184692,
1310 | 0,
1311 | 0.8257343769073486,
1312 | -0.040287263691425323,
1313 | 0.562618613243103,
1314 | 0,
1315 | 0.5579819083213806,
1316 | 0.2043645679950714,
1317 | -0.804295539855957,
1318 | 0,
1319 | 23.481035232543945,
1320 | -6.610706329345703,
1321 | 43.650535583496094,
1322 | 1,
1323 | ],
1324 | Float32Array [
1325 | 0.7270628809928894,
1326 | -0.5405710339546204,
1327 | 0.4232758581638336,
1328 | 0,
1329 | 0.5394104719161987,
1330 | 0.8311606049537659,
1331 | 0.1349381059408188,
1332 | 0,
1333 | -0.4247538149356842,
1334 | 0.1302110254764557,
1335 | 0.8958958983421326,
1336 | 0,
1337 | 23.743541717529297,
1338 | -5.563683986663818,
1339 | 42.109657287597656,
1340 | 1,
1341 | ],
1342 | Float32Array [
1343 | -0.5572721362113953,
1344 | -0.7579827308654785,
1345 | 0.33898332715034485,
1346 | 0,
1347 | -0.8230820894241333,
1348 | 0.4504533112049103,
1349 | -0.34587228298187256,
1350 | 0,
1351 | 0.10946907103061676,
1352 | -0.47175613045692444,
1353 | -0.8749073147773743,
1354 | 0,
1355 | 27.65191650390625,
1356 | -8.469552993774414,
1357 | 44.385005950927734,
1358 | 1,
1359 | ],
1360 | Float32Array [
1361 | 0.2273021936416626,
1362 | -0.1888088583946228,
1363 | 0.9553455114364624,
1364 | 0,
1365 | 0.7753335237503052,
1366 | -0.558498740196228,
1367 | -0.29485103487968445,
1368 | 0,
1369 | 0.5892296433448792,
1370 | 0.807731568813324,
1371 | 0.019441913813352585,
1372 | 0,
1373 | 19.49394989013672,
1374 | -2.396993398666382,
1375 | 39.339962005615234,
1376 | 1,
1377 | ],
1378 | Float32Array [
1379 | 0.42170655727386475,
1380 | -0.2869262099266052,
1381 | -0.8601378202438354,
1382 | 0,
1383 | -0.42949604988098145,
1384 | 0.7722336649894714,
1385 | -0.46817547082901,
1386 | 0,
1387 | 0.7985590696334839,
1388 | 0.5668584108352661,
1389 | 0.20242241024971008,
1390 | 0,
1391 | 17.38114356994629,
1392 | -0.9594568014144897,
1393 | 43.64936447143555,
1394 | 1,
1395 | ],
1396 | Float32Array [
1397 | 0.3596450984477997,
1398 | 0.11737680435180664,
1399 | -0.9256771206855774,
1400 | 0,
1401 | -0.5995972156524658,
1402 | 0.789192259311676,
1403 | -0.13288573920726776,
1404 | 0,
1405 | 0.7149394750595093,
1406 | 0.6028250455856323,
1407 | 0.3542080223560333,
1408 | 0,
1409 | 15.128959655761719,
1410 | 0.770934522151947,
1411 | 48.37097930908203,
1412 | 1,
1413 | ],
1414 | Float32Array [
1415 | 0.3852335810661316,
1416 | 0.7680676579475403,
1417 | -0.5115338563919067,
1418 | 0,
1419 | -0.5726765990257263,
1420 | 0.6336422562599182,
1421 | 0.5201336145401001,
1422 | 0,
1423 | 0.7236273288726807,
1424 | 0.09257055073976517,
1425 | 0.6839547753334045,
1426 | 0,
1427 | 15.128959655761719,
1428 | 0.770934522151947,
1429 | 48.37097930908203,
1430 | 1,
1431 | ],
1432 | Float32Array [
1433 | -0.7300897240638733,
1434 | -0.6809486150741577,
1435 | -0.05725223943591118,
1436 | 0,
1437 | -0.47344303131103516,
1438 | 0.4436309337615967,
1439 | 0.7609490156173706,
1440 | 0,
1441 | -0.4927683174610138,
1442 | 0.5826666951179504,
1443 | -0.646280825138092,
1444 | 0,
1445 | 10.305081367492676,
1446 | -7.853457450866699,
1447 | 49.2563591003418,
1448 | 1,
1449 | ],
1450 | Float32Array [
1451 | 0.5802935361862183,
1452 | -0.5540350079536438,
1453 | -0.5969126224517822,
1454 | 0,
1455 | -0.7584852576255798,
1456 | -0.6345741152763367,
1457 | -0.14837652444839478,
1458 | 0,
1459 | -0.296579509973526,
1460 | 0.5388513803482056,
1461 | -0.7884668111801147,
1462 | 0,
1463 | 5.643621921539307,
1464 | -12.201163291931152,
1465 | 48.89081573486328,
1466 | 1,
1467 | ],
1468 | Float32Array [
1469 | 0.7489371299743652,
1470 | 0.6435101628303528,
1471 | 0.15807506442070007,
1472 | 0,
1473 | 0.5925653576850891,
1474 | -0.5436302423477173,
1475 | -0.5944178700447083,
1476 | 0,
1477 | -0.296579509973526,
1478 | 0.5388513803482056,
1479 | -0.7884668111801147,
1480 | 0,
1481 | 11.587382316589355,
1482 | -17.875965118408203,
1483 | 42.77682876586914,
1484 | 1,
1485 | ],
1486 | Float32Array [
1487 | 0.9793927669525146,
1488 | 0.194644495844841,
1489 | 0.053881850093603134,
1490 | 0,
1491 | -0.19422584772109985,
1492 | 0.980871319770813,
1493 | -0.012951920740306377,
1494 | 0,
1495 | -0.055372219532728195,
1496 | 0.002219781279563904,
1497 | 0.9984633922576904,
1498 | 0,
1499 | 20.106964111328125,
1500 | -10.555676460266113,
1501 | 44.57502365112305,
1502 | 1,
1503 | ],
1504 | Float32Array [
1505 | 0.8525822758674622,
1506 | 0.3782253861427307,
1507 | 0.36062300205230713,
1508 | 0,
1509 | 0.34346580505371094,
1510 | 0.11454357206821442,
1511 | -0.9321539998054504,
1512 | 0,
1513 | -0.39387139678001404,
1514 | 0.9185996055603027,
1515 | -0.03224964439868927,
1516 | 0,
1517 | 20.760244369506836,
1518 | -10.38862419128418,
1519 | 45.620723724365234,
1520 | 1,
1521 | ],
1522 | Float32Array [
1523 | 0.5714821815490723,
1524 | 0.21725483238697052,
1525 | -0.7913333177566528,
1526 | 0,
1527 | -0.719912052154541,
1528 | -0.33011361956596375,
1529 | -0.6105337738990784,
1530 | 0,
1531 | -0.39387139678001404,
1532 | 0.9185996055603027,
1533 | -0.03224964439868927,
1534 | 0,
1535 | 22.581783294677734,
1536 | -9.580547332763672,
1537 | 46.39119338989258,
1538 | 1,
1539 | ],
1540 | Float32Array [
1541 | 0.9043940305709839,
1542 | 0.3368772566318512,
1543 | -0.2618875205516815,
1544 | 0,
1545 | -0.2265768051147461,
1546 | 0.8992204070091248,
1547 | 0.37425342202186584,
1548 | 0,
1549 | 0.3615720272064209,
1550 | -0.27913492918014526,
1551 | 0.8895782232284546,
1552 | 0,
1553 | 23.329172134399414,
1554 | -10.818304061889648,
1555 | 44.7757682800293,
1556 | 1,
1557 | ],
1558 | Float32Array [
1559 | -0.6400701999664307,
1560 | 0.6194452047348022,
1561 | 0.45453038811683655,
1562 | 0,
1563 | -0.6779201626777649,
1564 | -0.7337378859519958,
1565 | 0.04530814290046692,
1566 | 0,
1567 | 0.3615720272064209,
1568 | -0.27913492918014526,
1569 | 0.8895782232284546,
1570 | 0,
1571 | 25.14804458618164,
1572 | -10.140792846679688,
1573 | 44.24907302856445,
1574 | 1,
1575 | ],
1576 | Float32Array [
1577 | 0.26086562871932983,
1578 | 0.9463105201721191,
1579 | 0.19090688228607178,
1580 | 0,
1581 | -0.8951059579849243,
1582 | 0.1630338579416275,
1583 | 0.41497620940208435,
1584 | 0,
1585 | 0.3615720272064209,
1586 | -0.27913492918014526,
1587 | 0.8895782232284546,
1588 | 0,
1589 | 24.89754867553711,
1590 | -10.234101295471191,
1591 | 44.32160949707031,
1592 | 1,
1593 | ],
1594 | Float32Array [
1595 | 0.899629533290863,
1596 | 0.40502962470054626,
1597 | 0.16314886510372162,
1598 | 0,
1599 | -0.3023269772529602,
1600 | 0.8473595380783081,
1601 | -0.43655499815940857,
1602 | 0,
1603 | -0.3150635063648224,
1604 | 0.34341350197792053,
1605 | 0.8847612142562866,
1606 | 0,
1607 | 20.06972312927246,
1608 | -10.460785865783691,
1609 | 44.45249557495117,
1610 | 1,
1611 | ],
1612 | Float32Array [
1613 | 0.7489371299743652,
1614 | 0.6435101628303528,
1615 | 0.15807506442070007,
1616 | 0,
1617 | -0.5206888914108276,
1618 | 0.7190553545951843,
1619 | -0.46026355028152466,
1620 | 0,
1621 | -0.40984898805618286,
1622 | 0.2624005377292633,
1623 | 0.8735960125923157,
1624 | 0,
1625 | 16.081005096435547,
1626 | -14.014902114868164,
1627 | 43.725276947021484,
1628 | 1,
1629 | ],
1630 | Float32Array [
1631 | 0.11829128116369247,
1632 | 0.8399822115898132,
1633 | 0.5295630693435669,
1634 | 0,
1635 | 0.9476537704467773,
1636 | 0.06378870457410812,
1637 | -0.3128630220890045,
1638 | 0,
1639 | -0.296579509973526,
1640 | 0.5388513803482056,
1641 | -0.7884668111801147,
1642 | 0,
1643 | 11.742582321166992,
1644 | -18.02414321899414,
1645 | 42.6171875,
1646 | 1,
1647 | ],
1648 | Float32Array [
1649 | 0.5802935361862183,
1650 | -0.5540350079536438,
1651 | -0.5969126224517822,
1652 | 0,
1653 | -0.7275373935699463,
1654 | -0.6820399165153503,
1655 | -0.07423403114080429,
1656 | 0,
1657 | -0.3659899830818176,
1658 | 0.4773538112640381,
1659 | -0.7988647222518921,
1660 | 0,
1661 | 8.835235595703125,
1662 | -15.2483549118042,
1663 | 45.60779571533203,
1664 | 1,
1665 | ],
1666 | Float32Array [
1667 | -0.40524834394454956,
1668 | -0.8351771831512451,
1669 | -0.37182366847991943,
1670 | 0,
1671 | -0.8016831874847412,
1672 | 0.1291634738445282,
1673 | 0.5836274027824402,
1674 | 0,
1675 | -0.43940624594688416,
1676 | 0.5345987677574158,
1677 | -0.7218908071517944,
1678 | 0,
1679 | 5.521904945373535,
1680 | -12.314685821533203,
1681 | 48.88127136230469,
1682 | 1,
1683 | ],
1684 | Float32Array [
1685 | -0.40524834394454956,
1686 | -0.8351771831512451,
1687 | -0.37182366847991943,
1688 | 0,
1689 | -0.8016831874847412,
1690 | 0.1291634738445282,
1691 | 0.5836274027824402,
1692 | 0,
1693 | -0.43940624594688416,
1694 | 0.5345987677574158,
1695 | -0.7218908071517944,
1696 | 0,
1697 | 5.521904945373535,
1698 | -12.314685821533203,
1699 | 48.88127136230469,
1700 | 1,
1701 | ],
1702 | Float32Array [
1703 | 0.6303451061248779,
1704 | -0.5617841482162476,
1705 | 0.5357832312583923,
1706 | 0,
1707 | 0.42413055896759033,
1708 | -0.32884204387664795,
1709 | -0.843786895275116,
1710 | 0,
1711 | 0.6502140760421753,
1712 | 0.7591188549995422,
1713 | 0.030985888093709946,
1714 | 0,
1715 | 11.468490600585938,
1716 | -6.572696685791016,
1717 | 49.06472396850586,
1718 | 1,
1719 | ],
1720 | Float32Array [
1721 | 0.08737003803253174,
1722 | -0.5607438683509827,
1723 | -0.8233669400215149,
1724 | 0,
1725 | 0.5976953506469727,
1726 | -0.6317207217216492,
1727 | 0.49364903569221497,
1728 | 0,
1729 | -0.7969484925270081,
1730 | -0.5352526903152466,
1731 | 0.2799605429172516,
1732 | 0,
1733 | -4.1434760093688965,
1734 | -3.295677900314331,
1735 | 38.69938659667969,
1736 | 1,
1737 | ],
1738 | Float32Array [
1739 | 0.43992286920547485,
1740 | -0.03493577614426613,
1741 | -0.897355854511261,
1742 | 0,
1743 | 0.8256616592407227,
1744 | -0.37727102637290955,
1745 | 0.419463187456131,
1746 | 0,
1747 | -0.3532005846500397,
1748 | -0.9254437685012817,
1749 | -0.13712503015995026,
1750 | 0,
1751 | 0.729143500328064,
1752 | 2.2269716262817383,
1753 | 37.739906311035156,
1754 | 1,
1755 | ],
1756 | Float32Array [
1757 | 0.9827673435211182,
1758 | 0.023279547691345215,
1759 | -0.1833753138780594,
1760 | 0,
1761 | 0.1819995939731598,
1762 | 0.05159240588545799,
1763 | 0.9819442629814148,
1764 | 0,
1765 | 0.032319944351911545,
1766 | -0.9983969330787659,
1767 | 0.04646646976470947,
1768 | 0,
1769 | 0.667149007320404,
1770 | 2.1567041873931885,
1771 | 37.752113342285156,
1772 | 1,
1773 | ],
1774 | Float32Array [
1775 | -0.3389539420604706,
1776 | -0.05468452721834183,
1777 | -0.9392123818397522,
1778 | 0,
1779 | 0.940247654914856,
1780 | 0.014605341479182243,
1781 | -0.340177983045578,
1782 | 0,
1783 | 0.032319944351911545,
1784 | -0.9983969330787659,
1785 | 0.04646646976470947,
1786 | 0,
1787 | 16.955455780029297,
1788 | 2.5425374507904053,
1789 | 34.71286392211914,
1790 | 1,
1791 | ],
1792 | Float32Array [
1793 | -0.12811975181102753,
1794 | 0.12751443684101105,
1795 | -0.9835270643234253,
1796 | 0,
1797 | 0.9917126893997192,
1798 | 0.00691975187510252,
1799 | -0.12828892469406128,
1800 | 0,
1801 | -0.009552966803312302,
1802 | -0.9918126463890076,
1803 | -0.12734422087669373,
1804 | 0,
1805 | 11.827699661254883,
1806 | 1.7152591943740845,
1807 | 20.504291534423828,
1808 | 1,
1809 | ],
1810 | Float32Array [
1811 | 0.9446883201599121,
1812 | -0.05070258677005768,
1813 | 0.3240266442298889,
1814 | 0,
1815 | -0.32783037424087524,
1816 | -0.11720521748065948,
1817 | 0.9374381899833679,
1818 | 0,
1819 | -0.009552966803312302,
1820 | -0.9918126463890076,
1821 | -0.12734422087669373,
1822 | 0,
1823 | 15.299453735351562,
1824 | 2.4789445400238037,
1825 | 14.295936584472656,
1826 | 1,
1827 | ],
1828 | Float32Array [
1829 | -0.22944298386573792,
1830 | 0.04596107453107834,
1831 | -0.9722363352775574,
1832 | 0,
1833 | 0.9733123779296875,
1834 | 0.015296369791030884,
1835 | -0.228973850607872,
1836 | 0,
1837 | 0.00434776209294796,
1838 | -0.9988261461257935,
1839 | -0.04824411869049072,
1840 | 0,
1841 | 11.630845069885254,
1842 | 1.68350088596344,
1843 | 19.95882225036621,
1844 | 1,
1845 | ],
1846 | Float32Array [
1847 | -0.7212623953819275,
1848 | -0.05548227205872536,
1849 | -0.6904364228248596,
1850 | 0,
1851 | 0.6919075846672058,
1852 | -0.01119961403310299,
1853 | -0.7218993306159973,
1854 | 0,
1855 | 0.032319944351911545,
1856 | -0.9983969330787659,
1857 | 0.04646646976470947,
1858 | 0,
1859 | 17.580764770507812,
1860 | 2.5573487281799316,
1861 | 34.596187591552734,
1862 | 1,
1863 | ],
1864 | Float32Array [
1865 | 0.3584228754043579,
1866 | -0.8407067060470581,
1867 | -0.4058884382247925,
1868 | 0,
1869 | 0.3465271592140198,
1870 | -0.28390344977378845,
1871 | 0.8940458297729492,
1872 | 0,
1873 | -0.8668633103370667,
1874 | -0.4610978662967682,
1875 | 0.18957021832466125,
1876 | 0,
1877 | -4.081479072570801,
1878 | -3.2254128456115723,
1879 | 38.68717575073242,
1880 | 1,
1881 | ],
1882 | Float32Array [
1883 | -0.428552508354187,
1884 | 0.49489715695381165,
1885 | -0.7559230327606201,
1886 | 0,
1887 | 0.25473666191101074,
1888 | -0.7365227341651917,
1889 | -0.6266128420829773,
1890 | 0,
1891 | -0.8668633103370667,
1892 | -0.4610978662967682,
1893 | 0.18957021832466125,
1894 | 0,
1895 | 1.8589926958084106,
1896 | -17.159217834472656,
1897 | 31.96001434326172,
1898 | 1,
1899 | ],
1900 | Float32Array [
1901 | -0.02958093211054802,
1902 | 0.18589913845062256,
1903 | -0.9821234941482544,
1904 | 0,
1905 | 0.33844852447509766,
1906 | -0.9226529002189636,
1907 | -0.184836283326149,
1908 | 0,
1909 | -0.9405198097229004,
1910 | -0.337865948677063,
1911 | -0.03562433645129204,
1912 | 0,
1913 | -4.624226093292236,
1914 | -9.672325134277344,
1915 | 20.524274826049805,
1916 | 1,
1917 | ],
1918 | Float32Array [
1919 | 0.3329176902770996,
1920 | -0.937462329864502,
1921 | 0.1016383245587349,
1922 | 0,
1923 | -0.06773663312196732,
1924 | 0.0837329626083374,
1925 | 0.9941834211349487,
1926 | 0,
1927 | -0.9405198097229004,
1928 | -0.337865948677063,
1929 | -0.03562433645129204,
1930 | 0,
1931 | -3.357949733734131,
1932 | -12.518207550048828,
1933 | 14.08397388458252,
1934 | 1,
1935 | ],
1936 | Float32Array [
1937 | -0.151030033826828,
1938 | 0.29616579413414,
1939 | -0.9431203007698059,
1940 | 0,
1941 | 0.33268100023269653,
1942 | -0.8831841945648193,
1943 | -0.33061933517456055,
1944 | 0,
1945 | -0.9308669567108154,
1946 | -0.3636917173862457,
1947 | 0.034858617931604385,
1948 | 0,
1949 | -4.873117923736572,
1950 | -9.384902000427246,
1951 | 20.085256576538086,
1952 | 1,
1953 | ],
1954 | Float32Array [
1955 | -0.49415698647499084,
1956 | 0.7443469166755676,
1957 | -0.449173241853714,
1958 | 0,
1959 | 0.06600677967071533,
1960 | -0.4830492436885834,
1961 | -0.8731018304824829,
1962 | 0,
1963 | -0.8668633103370667,
1964 | -0.4610978662967682,
1965 | 0.18957021832466125,
1966 | 0,
1967 | 2.0870473384857178,
1968 | -17.694135665893555,
1969 | 31.701757431030273,
1970 | 1,
1971 | ],
1972 | Float32Array [
1973 | -0.5673703551292419,
1974 | -0.7951163053512573,
1975 | 0.21419788897037506,
1976 | 0,
1977 | 0.14473950862884521,
1978 | -0.3523622751235962,
1979 | -0.9246033430099487,
1980 | 0,
1981 | 0.8106425404548645,
1982 | -0.49358969926834106,
1983 | 0.31500473618507385,
1984 | 0,
1985 | 4.928685188293457,
1986 | -1.2658549547195435,
1987 | 51.53895950317383,
1988 | 1,
1989 | ],
1990 | ]
1991 | `;
1992 |
1993 | exports[`should calculate rotations of a #55 sequence of #10 frame 1`] = `
1994 | Array [
1995 | Float32Array [
1996 | 1,
1997 | -0.00007917818584246561,
1998 | -0,
1999 | 0,
2000 | 0.00007917818584246561,
2001 | 1,
2002 | 0,
2003 | 0,
2004 | 0,
2005 | -0,
2006 | 1,
2007 | 0,
2008 | 0.23384900391101837,
2009 | -2.2516889572143555,
2010 | 38.1921501159668,
2011 | 1,
2012 | ],
2013 | Float32Array [
2014 | 0.00034531948040239513,
2015 | -2.7115341083572275e-8,
2016 | 0.9999999403953552,
2017 | 0,
2018 | 0.9999999403953552,
2019 | -0.00007789669325575233,
2020 | -0.0003453493118286133,
2021 | 0,
2022 | 0.00007786688365740702,
2023 | 1,
2024 | -2.9590538019874657e-8,
2025 | 0,
2026 | -2.043086051940918,
2027 | -2.2515056133270264,
2028 | 36.95396423339844,
2029 | 1,
2030 | ],
2031 | Float32Array [
2032 | 0.0010843193158507347,
2033 | 9.153189921562443e-7,
2034 | 0.9999994039535522,
2035 | 0,
2036 | 0.9999994039535522,
2037 | -0.00008189665095414966,
2038 | -0.0010843491181731224,
2039 | 0,
2040 | 0.00008186579361790791,
2041 | 1,
2042 | -0.0000010339273330828291,
2043 | 0,
2044 | -1.3306695222854614,
2045 | -2.25156307220459,
2046 | 38.75086212158203,
2047 | 1,
2048 | ],
2049 | Float32Array [
2050 | 0.001119319349527359,
2051 | 9.124526059167692e-7,
2052 | 0.9999993443489075,
2053 | 0,
2054 | 0.9999993443489075,
2055 | -0.00008189668005798012,
2056 | -0.0011193491518497467,
2057 | 0,
2058 | 0.00008186579361790791,
2059 | 1,
2060 | -0.0000010339273330828291,
2061 | 0,
2062 | -1.3294826745986938,
2063 | -2.251559019088745,
2064 | 42.869468688964844,
2065 | 1,
2066 | ],
2067 | Float32Array [
2068 | 0.0011683193733915687,
2069 | 9.084396879188716e-7,
2070 | 0.9999992847442627,
2071 | 0,
2072 | 0.9999992847442627,
2073 | -0.0000818967237137258,
2074 | -0.0011683491757139564,
2075 | 0,
2076 | 0.00008186579361790791,
2077 | 1,
2078 | -0.0000010339273330828291,
2079 | 0,
2080 | -1.3281526565551758,
2081 | -2.2515549659729004,
2082 | 46.98807144165039,
2083 | 1,
2084 | ],
2085 | Float32Array [
2086 | 0.0011593194212764502,
2087 | 9.091767765312397e-7,
2088 | 0.9999992847442627,
2089 | 0,
2090 | 0.9999992847442627,
2091 | -0.00008189671643776819,
2092 | -0.0011593492235988379,
2093 | 0,
2094 | 0.00008186579361790791,
2095 | 1,
2096 | -0.0000010339273330828291,
2097 | 0,
2098 | -1.3266208171844482,
2099 | -2.2515509128570557,
2100 | 51.10667037963867,
2101 | 1,
2102 | ],
2103 | Float32Array [
2104 | -0.01827666163444519,
2105 | 0.0000025007313979585888,
2106 | 0.9998329281806946,
2107 | 0,
2108 | 0.9998329281806946,
2109 | -0.00008186357445083559,
2110 | 0.018276631832122803,
2111 | 0,
2112 | 0.00008186579361790791,
2113 | 1,
2114 | -0.0000010339273330828291,
2115 | 0,
2116 | -1.3253841400146484,
2117 | -2.251546859741211,
2118 | 55.22520446777344,
2119 | 1,
2120 | ],
2121 | Float32Array [
2122 | 0.18243485689163208,
2123 | -0.00001295334459427977,
2124 | 0.9832178950309753,
2125 | 0,
2126 | 0.9832178950309753,
2127 | -0.00008070441253948957,
2128 | -0.18243488669395447,
2129 | 0,
2130 | 0.00008168335625668988,
2131 | 1,
2132 | -0.0000020171451069472823,
2133 | 0,
2134 | -1.4065983295440674,
2135 | -2.251535654067993,
2136 | 59.66806411743164,
2137 | 1,
2138 | ],
2139 | Float32Array [
2140 | 0.00008173632522812113,
2141 | 1,
2142 | -0.0000017774940488379798,
2143 | 0,
2144 | -0.9999998807907104,
2145 | 0.00008176844858098775,
2146 | 0.0003457545826677233,
2147 | 0,
2148 | 0.00034572527511045337,
2149 | 0.0000017007357655529631,
2150 | 0.9999998807907104,
2151 | 0,
2152 | -1.0748282670974731,
2153 | -2.2486469745635986,
2154 | 61.05747985839844,
2155 | 1,
2156 | ],
2157 | Float32Array [
2158 | 0.00005377610432333313,
2159 | 0.9976134300231934,
2160 | -0.06904635578393936,
2161 | 0,
2162 | -0.9999998211860657,
2163 | 0.00007913461740827188,
2164 | 0.0003641237271949649,
2165 | 0,
2166 | 0.00036868517054244876,
2167 | 0.06904637068510056,
2168 | 0.9976133108139038,
2169 | 0,
2170 | -1.3217089176177979,
2171 | -0.5188261866569519,
2172 | 55.22527313232422,
2173 | 1,
2174 | ],
2175 | Float32Array [
2176 | -0.00402343412861228,
2177 | 0.9997472763061523,
2178 | -0.02211746573448181,
2179 | 0,
2180 | -0.99964839220047,
2181 | -0.0034414830151945353,
2182 | 0.02628583461046219,
2183 | 0,
2184 | 0.026203041896224022,
2185 | 0.02221548743546009,
2186 | 0.9994096755981445,
2187 | 0,
2188 | -1.3213655948638916,
2189 | 5.850712299346924,
2190 | 54.784427642822266,
2191 | 1,
2192 | ],
2193 | Float32Array [
2194 | 0.003987791948020458,
2195 | 0.9997427463531494,
2196 | -0.02232740819454193,
2197 | 0,
2198 | -0.9996485114097595,
2199 | 0.004570516757667065,
2200 | 0.02610774338245392,
2201 | 0,
2202 | 0.026203041896224022,
2203 | 0.02221548743546009,
2204 | 0.9994096755981445,
2205 | 0,
2206 | -1.3625763654708862,
2207 | 16.09080696105957,
2208 | 54.557884216308594,
2209 | 1,
2210 | ],
2211 | Float32Array [
2212 | -0.0019613842014223337,
2213 | 0.9939424395561218,
2214 | 0.1098843365907669,
2215 | 0,
2216 | -0.017025278881192207,
2217 | 0.10983540117740631,
2218 | -0.993803858757019,
2219 | 0,
2220 | -0.9998529553413391,
2221 | -0.003820012789219618,
2222 | 0.01670674979686737,
2223 | 0,
2224 | -1.3172128200531006,
2225 | 27.463441848754883,
2226 | 54.30390167236328,
2227 | 1,
2228 | ],
2229 | Float32Array [
2230 | 0.33022379875183105,
2231 | 0.8450896143913269,
2232 | -0.42044690251350403,
2233 | 0,
2234 | -0.9335064888000488,
2235 | 0.35832569003105164,
2236 | -0.01295979879796505,
2237 | 0,
2238 | 0.13970468938350677,
2239 | 0.3967697024345398,
2240 | 0.9072244167327881,
2241 | 0,
2242 | -0.31089723110198975,
2243 | 28.19413185119629,
2244 | 54.343841552734375,
2245 | 1,
2246 | ],
2247 | Float32Array [
2248 | 0.05149524658918381,
2249 | 0.9120585322380066,
2250 | -0.4068135917186737,
2251 | 0,
2252 | -0.9888530373573303,
2253 | 0.10355161875486374,
2254 | 0.1069871261715889,
2255 | 0,
2256 | 0.13970468938350677,
2257 | 0.3967697024345398,
2258 | 0.9072244167327881,
2259 | 0,
2260 | 0.39462506771087646,
2261 | 29.999664306640625,
2262 | 53.445560455322266,
2263 | 1,
2264 | ],
2265 | Float32Array [
2266 | -0.005021943245083094,
2267 | 0.99663907289505,
2268 | -0.08176358044147491,
2269 | 0,
2270 | -0.015556338243186474,
2271 | -0.08183262497186661,
2272 | -0.9965245723724365,
2273 | 0,
2274 | -0.999866247177124,
2275 | -0.003732518060132861,
2276 | 0.015915045514702797,
2277 | 0,
2278 | -1.2868106365203857,
2279 | 30.46283721923828,
2280 | 55.52643966674805,
2281 | 1,
2282 | ],
2283 | Float32Array [
2284 | -0.013501150533556938,
2285 | 0.7374573349952698,
2286 | -0.6752586364746094,
2287 | 0,
2288 | -0.009216208010911942,
2289 | -0.6753833889961243,
2290 | -0.7374091744422913,
2291 | 0,
2292 | -0.999866247177124,
2293 | -0.003732518060132861,
2294 | 0.015915045514702797,
2295 | 0,
2296 | -1.2969105243682861,
2297 | 32.467227935791016,
2298 | 55.36199951171875,
2299 | 1,
2300 | ],
2301 | Float32Array [
2302 | -0.009794315323233604,
2303 | 0.9162713885307312,
2304 | -0.4004381597042084,
2305 | 0,
2306 | -0.013087817467749119,
2307 | -0.4005405902862549,
2308 | -0.9161854982376099,
2309 | 0,
2310 | -0.999866247177124,
2311 | -0.003732518060132861,
2312 | 0.015915045514702797,
2313 | 0,
2314 | -1.2955195903778076,
2315 | 32.19118118286133,
2316 | 55.3846435546875,
2317 | 1,
2318 | ],
2319 | Float32Array [
2320 | 0.003987791948020458,
2321 | 0.9997427463531494,
2322 | -0.02232740819454193,
2323 | 0,
2324 | -0.018951337784528732,
2325 | -0.022248053923249245,
2326 | -0.9995726943016052,
2327 | 0,
2328 | -0.9998122453689575,
2329 | 0.004409254994243383,
2330 | 0.018857775256037712,
2331 | 0,
2332 | -1.3386496305465698,
2333 | 22.089263916015625,
2334 | 54.423919677734375,
2335 | 1,
2336 | ],
2337 | Float32Array [
2338 | 0.0006298619555309415,
2339 | 0.998636782169342,
2340 | 0.05219341069459915,
2341 | 0,
2342 | -0.02150355465710163,
2343 | 0.052194852381944656,
2344 | -0.9984052777290344,
2345 | 0,
2346 | -0.9997683167457581,
2347 | -0.0004934518947266042,
2348 | 0.021507147699594498,
2349 | 0,
2350 | -1.3172128200531006,
2351 | 27.463441848754883,
2352 | 54.30390167236328,
2353 | 1,
2354 | ],
2355 | Float32Array [
2356 | 0.9996564984321594,
2357 | -0.0005612339009530842,
2358 | -0.026197072118520737,
2359 | 0,
2360 | -0.000021108460714458488,
2361 | 0.9997530579566956,
2362 | -0.022222530096769333,
2363 | 0,
2364 | 0.026203041896224022,
2365 | 0.02221548743546009,
2366 | 0.9994096755981445,
2367 | 0,
2368 | -1.3636523485183716,
2369 | 16.358184814453125,
2370 | 54.551971435546875,
2371 | 1,
2372 | ],
2373 | Float32Array [
2374 | -0.00402343412861228,
2375 | 0.9997472763061523,
2376 | -0.02211746573448181,
2377 | 0,
2378 | -0.9998881816864014,
2379 | -0.003703690366819501,
2380 | 0.01447724923491478,
2381 | 0,
2382 | 0.014391642063856125,
2383 | 0.022173279896378517,
2384 | 0.9996504783630371,
2385 | 0,
2386 | -1.3434945344924927,
2387 | 11.349322319030762,
2388 | 54.66278076171875,
2389 | 1,
2390 | ],
2391 | Float32Array [
2392 | -0.008369917050004005,
2393 | 0.9998695254325867,
2394 | -0.013814365491271019,
2395 | 0,
2396 | -0.9997539520263672,
2397 | -0.008083579130470753,
2398 | 0.02065267786383629,
2399 | 0,
2400 | 0.02053828351199627,
2401 | 0.013983866199851036,
2402 | 0.9996911883354187,
2403 | 0,
2404 | -1.3213566541671753,
2405 | 6.01702880859375,
2406 | 54.77291488647461,
2407 | 1,
2408 | ],
2409 | Float32Array [
2410 | -0.004359164275228977,
2411 | 0.9991468191146851,
2412 | -0.041067495942115784,
2413 | 0,
2414 | -0.9999347925186157,
2415 | -0.003922284115105867,
2416 | 0.010711964219808578,
2417 | 0,
2418 | 0.010541713796555996,
2419 | 0.04111155867576599,
2420 | 0.9990988373756409,
2421 | 0,
2422 | -1.3213566541671753,
2423 | 6.01702880859375,
2424 | 54.77291488647461,
2425 | 1,
2426 | ],
2427 | Float32Array [
2428 | -0.00010396599100204185,
2429 | -0.9976136088371277,
2430 | -0.06904441118240356,
2431 | 0,
2432 | -1,
2433 | 0.00007913340232335031,
2434 | 0.0003628671111073345,
2435 | 0,
2436 | -0.0003565113293007016,
2437 | 0.06904439628124237,
2438 | -0.9976134896278381,
2439 | 0,
2440 | -1.3219836950302124,
2441 | -3.9842681884765625,
2442 | 55.22527313232422,
2443 | 1,
2444 | ],
2445 | Float32Array [
2446 | -0.0041815731674432755,
2447 | -0.9997466802597046,
2448 | -0.022115519270300865,
2449 | 0,
2450 | -0.9996480941772461,
2451 | 0.003599748481065035,
2452 | 0.02628457359969616,
2453 | 0,
2454 | -0.02619827911257744,
2455 | 0.02221759781241417,
2456 | -0.9994097948074341,
2457 | 0,
2458 | -1.3226474523544312,
2459 | -10.353808403015137,
2460 | 54.78443908691406,
2461 | 1,
2462 | ],
2463 | Float32Array [
2464 | 0.003829655470326543,
2465 | -0.9997434020042419,
2466 | -0.02232545055449009,
2467 | 0,
2468 | -0.9996494650840759,
2469 | -0.004412251524627209,
2470 | 0.026106497272849083,
2471 | 0,
2472 | -0.02619827911257744,
2473 | 0.02221759781241417,
2474 | -0.9994097948074341,
2475 | 0,
2476 | -1.3654780387878418,
2477 | -20.593894958496094,
2478 | 54.55791473388672,
2479 | 1,
2480 | ],
2481 | Float32Array [
2482 | -0.002118770033121109,
2483 | -0.9939418435096741,
2484 | 0.1098862811923027,
2485 | 0,
2486 | -0.017041411250829697,
2487 | -0.10983472317457199,
2488 | -0.9938037395477295,
2489 | 0,
2490 | 0.9998525977134705,
2491 | -0.003978291060775518,
2492 | -0.016705486923456192,
2493 | 0,
2494 | -1.321913480758667,
2495 | -31.966537475585938,
2496 | 54.303951263427734,
2497 | 1,
2498 | ],
2499 | Float32Array [
2500 | 0.3300907015800476,
2501 | -0.845142662525177,
2502 | -0.42044487595558167,
2503 | 0,
2504 | -0.9335633516311646,
2505 | -0.35817793011665344,
2506 | -0.012960273772478104,
2507 | 0,
2508 | -0.13964080810546875,
2509 | 0.39678993821144104,
2510 | -0.9072254300117493,
2511 | 0,
2512 | -0.3157133162021637,
2513 | -32.6973876953125,
2514 | 54.343894958496094,
2515 | 1,
2516 | ],
2517 | Float32Array [
2518 | 0.051351480185985565,
2519 | -0.9120674133300781,
2520 | -0.40681177377700806,
2521 | 0,
2522 | -0.9888697266578674,
2523 | -0.10339487344026566,
2524 | 0.10698609799146652,
2525 | 0,
2526 | -0.13964080810546875,
2527 | 0.39678993821144104,
2528 | -0.9072254300117493,
2529 | 0,
2530 | 0.3895251154899597,
2531 | -34.50303268432617,
2532 | 53.44561767578125,
2533 | 1,
2534 | ],
2535 | Float32Array [
2536 | -0.005179515574127436,
2537 | -0.9966384172439575,
2538 | -0.08176164329051971,
2539 | 0,
2540 | -0.015542143024504185,
2541 | 0.08183304965496063,
2542 | -0.9965248703956604,
2543 | 0,
2544 | 0.9998658895492554,
2545 | -0.003890796797350049,
2546 | -0.01591378264129162,
2547 | 0,
2548 | -1.2919872999191284,
2549 | -34.96593475341797,
2550 | 55.526493072509766,
2551 | 1,
2552 | ],
2553 | Float32Array [
2554 | -0.013616975396871567,
2555 | -0.7374565601348877,
2556 | -0.6752573251724243,
2557 | 0,
2558 | -0.009108434431254864,
2559 | 0.6753833293914795,
2560 | -0.7374105453491211,
2561 | 0,
2562 | 0.9998658895492554,
2563 | -0.003890796797350049,
2564 | -0.01591378264129162,
2565 | 0,
2566 | -1.3024040460586548,
2567 | -36.9703254699707,
2568 | 55.362056732177734,
2569 | 1,
2570 | ],
2571 | Float32Array [
2572 | -0.009938773699104786,
2573 | -0.9162706732749939,
2574 | -0.4004364013671875,
2575 | 0,
2576 | -0.013023301027715206,
2577 | 0.4005407691001892,
2578 | -0.9161863923072815,
2579 | 0,
2580 | 0.9998658895492554,
2581 | -0.003890796797350049,
2582 | -0.01591378264129162,
2583 | 0,
2584 | -1.3009694814682007,
2585 | -36.694278717041016,
2586 | 55.38470458984375,
2587 | 1,
2588 | ],
2589 | Float32Array [
2590 | 0.00047180629917420447,
2591 | -0.9986367225646973,
2592 | 0.052195366472005844,
2593 | 0,
2594 | -0.021510563790798187,
2595 | -0.05219348147511482,
2596 | -0.9984052181243896,
2597 | 0,
2598 | 0.9997684955596924,
2599 | -0.0006517266156151891,
2600 | -0.021505890414118767,
2601 | 0,
2602 | -1.3252018690109253,
2603 | -31.976806640625,
2604 | 54.144927978515625,
2605 | 1,
2606 | ],
2607 | Float32Array [
2608 | 0.003829655470326543,
2609 | -0.9997434020042419,
2610 | -0.02232545055449009,
2611 | 0,
2612 | -0.021472983062267303,
2613 | 0.02223820425570011,
2614 | -0.9995219707489014,
2615 | 0,
2616 | 0.9997621178627014,
2617 | 0.004307187162339687,
2618 | -0.02138233557343483,
2619 | 0,
2620 | -1.3425002098083496,
2621 | -26.592355728149414,
2622 | 54.42395782470703,
2623 | 1,
2624 | ],
2625 | Float32Array [
2626 | 0.9996568560600281,
2627 | 0.00040296732913702726,
2628 | -0.026195820420980453,
2629 | 0,
2630 | -0.00017924743588082492,
2631 | -0.9997531175613403,
2632 | -0.02222057804465294,
2633 | 0,
2634 | -0.02619827911257744,
2635 | 0.02221759781241417,
2636 | -0.9994097948074341,
2637 | 0,
2638 | -1.3665963411331177,
2639 | -20.861276626586914,
2640 | 54.552001953125,
2641 | 1,
2642 | ],
2643 | Float32Array [
2644 | -0.0041815731674432755,
2645 | -0.9997466802597046,
2646 | -0.022115519270300865,
2647 | 0,
2648 | -0.999860942363739,
2649 | 0.003822872182354331,
2650 | 0.016238346695899963,
2651 | 0,
2652 | -0.01614966243505478,
2653 | 0.02218029648065567,
2654 | -0.9996235370635986,
2655 | 0,
2656 | -1.3456461429595947,
2657 | -15.85241413116455,
2658 | 54.662803649902344,
2659 | 1,
2660 | ],
2661 | Float32Array [
2662 | -0.004516185261309147,
2663 | -0.9991462826728821,
2664 | -0.041065555065870285,
2665 | 0,
2666 | -0.9999343752861023,
2667 | 0.004079565871506929,
2668 | 0.010710631497204304,
2669 | 0,
2670 | -0.010533931665122509,
2671 | 0.04111117869615555,
2672 | -0.9990990161895752,
2673 | 0,
2674 | -1.3226648569107056,
2675 | -10.520124435424805,
2676 | 54.77293014526367,
2677 | 1,
2678 | ],
2679 | Float32Array [
2680 | -0.004516185261309147,
2681 | -0.9991462826728821,
2682 | -0.041065555065870285,
2683 | 0,
2684 | -0.9999343752861023,
2685 | 0.004079565871506929,
2686 | 0.010710631497204304,
2687 | 0,
2688 | -0.010533931665122509,
2689 | 0.04111117869615555,
2690 | -0.9990990161895752,
2691 | 0,
2692 | -1.3226648569107056,
2693 | -10.520124435424805,
2694 | 54.77293014526367,
2695 | 1,
2696 | ],
2697 | Float32Array [
2698 | 0.0011593194212764502,
2699 | 9.091767765312397e-7,
2700 | 0.9999992847442627,
2701 | 0,
2702 | 0.9999992847442627,
2703 | -0.00008189671643776819,
2704 | -0.0011593492235988379,
2705 | 0,
2706 | 0.00008186579361790791,
2707 | 1,
2708 | -0.0000010339273330828291,
2709 | 0,
2710 | -1.3249223232269287,
2711 | -2.251546859741211,
2712 | 55.33281326293945,
2713 | 1,
2714 | ],
2715 | Float32Array [
2716 | 0.06316575407981873,
2717 | -0.08451366424560547,
2718 | -0.9944183230400085,
2719 | 0,
2720 | 0.9980021119117737,
2721 | 0.006711830850690603,
2722 | 0.06282294541597366,
2723 | 0,
2724 | 0.0013649967731907964,
2725 | -0.9963998198509216,
2726 | 0.08476880937814713,
2727 | 0,
2728 | -2.0433802604675293,
2729 | -5.965084552764893,
2730 | 36.9539680480957,
2731 | 1,
2732 | ],
2733 | Float32Array [
2734 | 0.05257479101419449,
2735 | 0.07044802606105804,
2736 | -0.996129035949707,
2737 | 0,
2738 | 0.9986156225204468,
2739 | -0.005363597068935633,
2740 | 0.05232668295502663,
2741 | 0,
2742 | -0.0016564909601584077,
2743 | -0.9975010752677917,
2744 | -0.0706324577331543,
2745 | 0,
2746 | -2.0427918434143066,
2747 | 1.4620733261108398,
2748 | 36.95396041870117,
2749 | 1,
2750 | ],
2751 | Float32Array [
2752 | 0.08525795489549637,
2753 | 0.13884684443473816,
2754 | -0.9866369366645813,
2755 | 0,
2756 | 0.9963585734367371,
2757 | -0.011101548559963703,
2758 | 0.08453569561243057,
2759 | 0,
2760 | 0.0007843512576073408,
2761 | -0.9902515411376953,
2762 | -0.13928772509098053,
2763 | 0,
2764 | -2.042797327041626,
2765 | 1.3675754070281982,
2766 | 36.95396041870117,
2767 | 1,
2768 | ],
2769 | Float32Array [
2770 | -0.07702001184225082,
2771 | 0.13881421089172363,
2772 | -0.987318754196167,
2773 | 0,
2774 | 0.997029185295105,
2775 | 0.01150231808423996,
2776 | -0.07616034150123596,
2777 | 0,
2778 | 0.0007843512576073408,
2779 | -0.9902515411376953,
2780 | -0.13928772509098053,
2781 | 0,
2782 | -0.6297388672828674,
2783 | 3.668811798095703,
2784 | 20.601518630981445,
2785 | 1,
2786 | ],
2787 | Float32Array [
2788 | -0.00034566535032354295,
2789 | -0.0000011582384331632056,
2790 | -0.9999998807907104,
2791 | 0,
2792 | 0.9999998807907104,
2793 | -0.00007933702727314085,
2794 | -0.0003456989652477205,
2795 | 0,
2796 | -0.00007930747960926965,
2797 | -0.9999999403953552,
2798 | 0.0000011880479178216774,
2799 | 0,
2800 | -1.7949113845825195,
2801 | 5.768816947937012,
2802 | 5.665184020996094,
2803 | 1,
2804 | ],
2805 | Float32Array [
2806 | 0.9999998211860657,
2807 | -0.00007933701999718323,
2808 | -0.00034607035922817886,
2809 | 0,
2810 | 0.00034603674430400133,
2811 | 0.000001158208874585398,
2812 | 0.9999998211860657,
2813 | 0,
2814 | -0.00007930747960926965,
2815 | -0.9999999403953552,
2816 | 0.0000011880479178216774,
2817 | 0,
2818 | 2.4478275775909424,
2819 | 5.7684736251831055,
2820 | -0.09494777768850327,
2821 | 1,
2822 | ],
2823 | Float32Array [
2824 | -0.035920675843954086,
2825 | 0.06185802072286606,
2826 | -0.9974382519721985,
2827 | 0,
2828 | 0.9993525147438049,
2829 | 0.0042441533878445625,
2830 | -0.035726431757211685,
2831 | 0,
2832 | 0.0020233469549566507,
2833 | -0.9980758428573608,
2834 | -0.06197042018175125,
2835 | 0,
2836 | -1.8396415710449219,
2837 | 5.849437713623047,
2838 | 5.091775894165039,
2839 | 1,
2840 | ],
2841 | Float32Array [
2842 | -0.9856967329978943,
2843 | 0.02270817756652832,
2844 | -0.16699175536632538,
2845 | 0,
2846 | 0.16852687299251556,
2847 | 0.1374264508485794,
2848 | -0.9760699272155762,
2849 | 0,
2850 | 0.0007843512576073408,
2851 | -0.9902515411376953,
2852 | -0.13928772509098053,
2853 | 0,
2854 | -0.5754914283752441,
2855 | 3.757155418395996,
2856 | 19.97374725341797,
2857 | 1,
2858 | ],
2859 | Float32Array [
2860 | 0.08523629605770111,
2861 | -0.138863205909729,
2862 | -0.9866366982460022,
2863 | 0,
2864 | 0.9963603019714355,
2865 | 0.010944337584078312,
2866 | 0.08453594893217087,
2867 | 0,
2868 | -0.0009408225305378437,
2869 | -0.9902511835098267,
2870 | 0.1392906755208969,
2871 | 0,
2872 | -2.0433709621429443,
2873 | -5.870586395263672,
2874 | 36.9539680480957,
2875 | 1,
2876 | ],
2877 | Float32Array [
2878 | -0.07704166322946548,
2879 | -0.13880492746829987,
2880 | -0.9873185157775879,
2881 | 0,
2882 | 0.9970273971557617,
2883 | -0.011660105548799038,
2884 | -0.07616005092859268,
2885 | 0,
2886 | -0.0009408225305378437,
2887 | -0.9902511835098267,
2888 | 0.1392906755208969,
2889 | 0,
2890 | -0.6306714415550232,
2891 | -8.172094345092773,
2892 | 20.601531982421875,
2893 | 1,
2894 | ],
2895 | Float32Array [
2896 | -0.0003453836543485522,
2897 | -0.0000016883919897736632,
2898 | -1,
2899 | 0,
2900 | 0.9999998807907104,
2901 | -0.00007869795081205666,
2902 | -0.000345430220477283,
2903 | 0,
2904 | -0.00007866453961469233,
2905 | -1.0000001192092896,
2906 | 0.0000017700795069686137,
2907 | 0,
2908 | -1.7961714267730713,
2909 | -10.271960258483887,
2910 | 5.665201187133789,
2911 | 1,
2912 | ],
2913 | Float32Array [
2914 | 0.9999998211860657,
2915 | -0.00007869794353609905,
2916 | -0.0003458016144577414,
2917 | 0,
2918 | 0.0003457550483290106,
2919 | 0.000001688362658569531,
2920 | 0.9999999403953552,
2921 | 0,
2922 | -0.00007866453961469233,
2923 | -1.0000001192092896,
2924 | 0.0000017700795069686137,
2925 | 0,
2926 | 2.4465701580047607,
2927 | -10.272303581237793,
2928 | -0.09493015706539154,
2929 | 1,
2930 | ],
2931 | Float32Array [
2932 | -0.02189643681049347,
2933 | -0.03546428307890892,
2934 | -0.9991310834884644,
2935 | 0,
2936 | 0.9997584819793701,
2937 | -0.002648639027029276,
2938 | -0.021816225722432137,
2939 | 0,
2940 | -0.0018726097187027335,
2941 | -0.9993675351142883,
2942 | 0.03551376610994339,
2943 | 0,
2944 | -1.8409152030944824,
2945 | -10.352574348449707,
2946 | 5.091793060302734,
2947 | 1,
2948 | ],
2949 | Float32Array [
2950 | -0.985700249671936,
2951 | -0.022552892565727234,
2952 | -0.16699199378490448,
2953 | 0,
2954 | 0.1685054451227188,
2955 | -0.13745594024658203,
2956 | -0.9760696291923523,
2957 | 0,
2958 | -0.0009408225305378437,
2959 | -0.9902511835098267,
2960 | 0.1392906755208969,
2961 | 0,
2962 | -0.5764379501342773,
2963 | -8.260448455810547,
2964 | 19.9737606048584,
2965 | 1,
2966 | ],
2967 | Float32Array [
2968 | -0.00008186509512597695,
2969 | -0.9999999403953552,
2970 | 0.0000014133714785202756,
2971 | 0,
2972 | 0.9953628778457642,
2973 | -0.00008164426253642887,
2974 | -0.09619037806987762,
2975 | 0,
2976 | 0.09619034826755524,
2977 | -0.000006500161816802574,
2978 | 0.9953628778457642,
2979 | 0,
2980 | -8.229607582092285,
2981 | -2.484844207763672,
2982 | 48.55447769165039,
2983 | 1,
2984 | ],
2985 | ]
2986 | `;
2987 |
2988 | exports[`should calculate valid quaternion of bone #0 sequence #0 frame #0 1`] = `
2989 | Float32Array [
2990 | -0.004799471702426672,
2991 | 0.15218494832515717,
2992 | 0.018867794424295425,
2993 | 0.988160252571106,
2994 | ]
2995 | `;
2996 |
2997 | exports[`should calculate valid quaternion of bone #6 sequence #55 frame #10 1`] = `
2998 | Float32Array [
2999 | 0,
3000 | 0,
3001 | -0.00971834734082222,
3002 | 0.9999527931213379,
3003 | ]
3004 | `;
3005 |
3006 | exports[`should calculate valid quaternion of bone #15 sequence #2 frame #23 1`] = `
3007 | Float32Array [
3008 | 0.014472931623458862,
3009 | 0.09690660238265991,
3010 | -0.3360680937767029,
3011 | 0.9367272257804871,
3012 | ]
3013 | `;
3014 |
--------------------------------------------------------------------------------
/lib/__tests__/binaryReader.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs'
2 | import * as path from 'path'
3 | import * as FastDataView from 'fast-dataview'
4 | import * as structs from '../../const/structs'
5 | import { readStruct, readStructMultiple } from '../binaryReader'
6 | import {
7 | int8,
8 | byte,
9 | uint8,
10 | ubyte,
11 | short,
12 | int16,
13 | uint16,
14 | ushort,
15 | int32,
16 | int,
17 | uint32,
18 | uint,
19 | float32,
20 | float,
21 | float64,
22 | double,
23 | string,
24 | array,
25 | vec3,
26 | skip
27 | } from '../dataTypes'
28 |
29 | // Loading model for testing
30 | const leetPath = path.resolve(__dirname, '../../__mock__/leet.mdl')
31 | const leetBuffer: ArrayBuffer = fs.readFileSync(leetPath).buffer
32 |
33 | test('should parse some struct in binary file', () => {
34 | const dataView = new FastDataView(leetBuffer)
35 | const header = readStruct(dataView, structs.header)
36 | expect(header).toMatchSnapshot('leet header')
37 | })
38 |
39 | test('should parse some struct in binary file with offset', () => {
40 | const dataView = new FastDataView(leetBuffer)
41 | const header = readStruct(dataView, structs.header)
42 | const hitBox = readStruct(dataView, structs.boundingBox, header.hitBoxIndex)
43 | expect(hitBox).toMatchSnapshot('some hitbox')
44 | })
45 |
46 | test('should parse multiple structs in binary file', () => {
47 | const dataView = new FastDataView(leetBuffer)
48 | const header = readStruct(dataView, structs.header)
49 | const texturesInfo = readStructMultiple(dataView, structs.texture, header.textureIndex, header.numTextures)
50 | expect(texturesInfo).toMatchSnapshot('leet textures info')
51 | })
52 |
53 | test('should parse multiple structs in binary file with offset', () => {
54 | const dataView = new FastDataView(leetBuffer)
55 | const header = readStructMultiple(dataView, structs.header)[0]
56 | expect(header).toMatchSnapshot('leet header')
57 | })
58 |
59 | test('should get byte from buffer', () => {
60 | const dataView = new DataView(new Int8Array([-1]).buffer)
61 | expect(int8.getValue(dataView, 0)).toBe(-1)
62 | expect(byte.getValue(dataView, 0)).toBe(-1)
63 | })
64 |
65 | test('should get unsigned byte from buffer', () => {
66 | const dataView = new DataView(new Uint8Array([-1]).buffer)
67 | expect(uint8.getValue(dataView, 0)).toBe(255)
68 | expect(ubyte.getValue(dataView, 0)).toBe(255)
69 | })
70 |
71 | test('should get short integer from buffer', () => {
72 | const dataView = new DataView(new Int16Array([-1]).buffer)
73 | expect(int16.getValue(dataView, 0)).toBe(-1)
74 | expect(short.getValue(dataView, 0)).toBe(-1)
75 | })
76 |
77 | test('should get unsigned short integer from buffer', () => {
78 | const dataView = new DataView(new Uint16Array([-1]).buffer)
79 | expect(uint16.getValue(dataView, 0)).toBe(65535)
80 | expect(ushort.getValue(dataView, 0)).toBe(65535)
81 | })
82 |
83 | test('should get integer from buffer', () => {
84 | const dataView = new DataView(new Int32Array([-1]).buffer)
85 | expect(int32.getValue(dataView, 0)).toBe(-1)
86 | expect(int.getValue(dataView, 0)).toBe(-1)
87 | })
88 |
89 | test('should get unsigned integer from buffer', () => {
90 | const dataView = new DataView(new Uint32Array([-1]).buffer)
91 | expect(uint32.getValue(dataView, 0)).toBe(4294967295)
92 | expect(uint.getValue(dataView, 0)).toBe(4294967295)
93 | })
94 |
95 | test('should get float from buffer', () => {
96 | const dataView = new DataView(new Float32Array([-Math.PI]).buffer)
97 | // Inaccurate value of PI
98 | expect(float32.getValue(dataView, 0)).toBe(-3.1415927410125732)
99 | expect(float.getValue(dataView, 0)).toBe(-3.1415927410125732)
100 | })
101 |
102 | test('should get double float from buffer', () => {
103 | const dataView = new DataView(new Float64Array([-Math.PI]).buffer)
104 | expect(float64.getValue(dataView, 0)).toBe(-Math.PI)
105 | expect(double.getValue(dataView, 0)).toBe(-Math.PI)
106 | })
107 |
108 | test('should get string from buffer', () => {
109 | const dataView = new DataView(new Int8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]).buffer)
110 | expect(string(11).getValue(dataView, 0)).toBe('hello world')
111 | })
112 |
113 | test('should get array of bytes from buffer', () => {
114 | const dataView = new DataView(new Int8Array([1, 2, 3]).buffer)
115 | expect(array(3, byte).getValue(dataView, 0)).toEqual(new Int8Array([1, 2, 3]))
116 | })
117 |
118 | test('should get vector 3 from buffer', () => {
119 | const dataView = new DataView(new Float32Array([0.5, 0.25, 0.125]).buffer)
120 | expect(vec3.getValue(dataView, 0)).toEqual(new Float32Array([0.5, 0.25, 0.125]))
121 | })
122 |
123 | test('should create skipping data type', () => {
124 | expect(skip(8).byteLength).toEqual(8)
125 | expect(skip(int).byteLength).toEqual(4)
126 | })
127 |
--------------------------------------------------------------------------------
/lib/__tests__/geometryBuilder.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path'
2 | import * as fs from 'fs'
3 | import { parseModel } from '../modelDataParser'
4 | import { readFacesData, countVertices, getTriangleSeriesType } from '../geometryBuilder'
5 | import { TRIANGLE_FAN, TRIANGLE_STRIP } from '../../const/constants'
6 |
7 | const leetPath = path.resolve(__dirname, '../../__mock__/leet.mdl')
8 | const leetBuffer: ArrayBuffer = fs.readFileSync(leetPath).buffer
9 | const leetModelData = parseModel(leetBuffer)
10 |
11 | const meshDataPath = [[0, 0, 0], [0, 0, 1], [1, 1, 0]]
12 |
13 | describe('test geometry building', () => {
14 | test('detect triangles series type', () => {
15 | expect(getTriangleSeriesType(-1)).toBe(TRIANGLE_FAN)
16 | expect(getTriangleSeriesType(1)).toBe(TRIANGLE_STRIP)
17 | })
18 |
19 | test('speed of geometry building', () => {
20 | for (const [a, b, c] of meshDataPath) {
21 | readFacesData(leetModelData.triangles[a][b][c], leetModelData.vertices[a][b], leetModelData.textures[a])
22 | }
23 | })
24 |
25 | test('should build the proper number of vertices', () => {
26 | expect(countVertices(leetModelData.triangles[0][0][0])).toBe(2220)
27 | expect(countVertices(leetModelData.triangles[0][0][1])).toBe(36)
28 | expect(countVertices(leetModelData.triangles[1][1][0])).toBe(132)
29 | })
30 |
31 | test('should build geometry buffer', () => {
32 | for (const [a, b, c] of meshDataPath) {
33 | expect(
34 | readFacesData(leetModelData.triangles[a][b][c], leetModelData.vertices[a][b], leetModelData.textures[a])
35 | .vertices
36 | ).toMatchSnapshot(`geometry buffer`)
37 | }
38 | })
39 |
40 | test('should building uv map', () => {
41 | for (const [a, b, c] of meshDataPath) {
42 | expect(
43 | readFacesData(leetModelData.triangles[a][b][c], leetModelData.vertices[a][b], leetModelData.textures[a]).uv
44 | ).toMatchSnapshot('uv buffer')
45 | }
46 | })
47 | })
48 |
--------------------------------------------------------------------------------
/lib/__tests__/geometryTransformer.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path'
2 | import * as fs from 'fs'
3 | import { parseModel } from '../modelDataParser'
4 | import { calcBoneQuaternion, calcRotations } from '../geometryTransformer'
5 |
6 | const leetPath = path.resolve(__dirname, '../../__mock__/leet.mdl')
7 | const leetBuffer: ArrayBuffer = fs.readFileSync(leetPath).buffer
8 | const leetModelData = parseModel(leetBuffer)
9 |
10 | //
11 | ;[[0, 0, 0], [15, 2, 23], [6, 55, 10]].forEach(([boneIndex, sequenceIndex, frame]) => {
12 | test(`should calculate valid quaternion of bone #${boneIndex} sequence #${sequenceIndex} frame #${frame}`, () => {
13 | const bone = leetModelData.bones[boneIndex]
14 | const animOffset = leetModelData.animations[sequenceIndex][boneIndex].offset
15 | const animValues = leetModelData.animValues
16 |
17 | expect(calcBoneQuaternion(frame, bone, animOffset, animValues, boneIndex, sequenceIndex, 0)).toMatchSnapshot()
18 | })
19 | })
20 |
21 | //
22 | ;[[0, 0], [2, 23], [55, 10]].forEach(([sequenceIndex, frame]) => {
23 | test(`should calculate rotations of a #${sequenceIndex} sequence of #${frame} frame`, () => {
24 | expect(calcRotations(leetModelData, sequenceIndex, frame)).toMatchSnapshot()
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/lib/__tests__/modelDataParser.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs'
2 | import * as path from 'path'
3 | import * as FastDataView from 'fast-dataview'
4 | import { intersection } from 'ramda'
5 | import * as ModelParser from '../modelDataParser'
6 |
7 | // Loading model for testing
8 | const leetPath = path.resolve(__dirname, '../../__mock__/leet.mdl')
9 | const leetBuffer: ArrayBuffer = fs.readFileSync(leetPath).buffer
10 |
11 | const ratamahattaPath = path.resolve(__dirname, '../../__mock__/ratamahatta.md2')
12 | const ratamahattaBuffer: ArrayBuffer = fs.readFileSync(ratamahattaPath).buffer
13 |
14 | const dataView = new FastDataView(leetBuffer)
15 | const header = ModelParser.parseHeader(dataView)
16 |
17 | describe('parsing model parts', () => {
18 | test('should parse header', () => {
19 | const header = ModelParser.parseHeader(dataView)
20 | expect(header).toMatchSnapshot('leet header')
21 | })
22 |
23 | test('should parse bones', () => {
24 | const bones = ModelParser.parseBones(dataView, header.boneIndex, header.numBones)
25 | expect(bones).toMatchSnapshot('leet bones')
26 | })
27 |
28 | test('should parse bone controllers', () => {
29 | const boneControllers = ModelParser.parseBoneControllers(
30 | dataView,
31 | header.boneControllerIndex,
32 | header.numBoneControllers
33 | )
34 | expect(boneControllers).toMatchSnapshot('leet bone controllers')
35 | })
36 |
37 | test('should parse attachments', () => {
38 | const attachments = ModelParser.parseAttachments(dataView, header.attachmentIndex, header.numAttachments)
39 | expect(attachments).toMatchSnapshot('leet attachments')
40 | })
41 |
42 | test('should parse hitboxes', () => {
43 | const hitBoxes = ModelParser.parseHitboxes(dataView, header.hitBoxIndex, header.numHitboxes)
44 | expect(hitBoxes).toMatchSnapshot('leet hitboxes')
45 | })
46 |
47 | test('should parse sequences', () => {
48 | const sequences = ModelParser.parseSequences(dataView, header.seqIndex, header.numSeq)
49 | expect(sequences).toMatchSnapshot('leet sequences')
50 | })
51 |
52 | test('should parse sequence groups', () => {
53 | const sequenceGroups = ModelParser.parseSequenceGroups(dataView, header.seqGroupIndex, header.numSeqGroups)
54 | expect(sequenceGroups).toMatchSnapshot('leet sequence groups')
55 | })
56 |
57 | test('should parse bodyparts', () => {
58 | const bodyParts = ModelParser.parseBodyParts(dataView, header.bodyPartIndex, header.numBodyParts)
59 | expect(bodyParts).toMatchSnapshot('leet body parts')
60 | })
61 |
62 | test('should parse textures', () => {
63 | const textures = ModelParser.parseTextures(dataView, header.textureIndex, header.numTextures)
64 | expect(textures).toMatchSnapshot('leet textures info')
65 | })
66 |
67 | test('should parse skin references', () => {
68 | const skinRef = ModelParser.parseSkinRef(dataView.buffer, header.skinIndex, header.numSkinRef)
69 | expect(skinRef).toMatchSnapshot('leet skin references')
70 | })
71 |
72 | test('should parse submodels', () => {
73 | const bodyParts = ModelParser.parseBodyParts(dataView, header.bodyPartIndex, header.numBodyParts)
74 | const subModels = ModelParser.parseSubModel(dataView, bodyParts)
75 |
76 | expect(subModels).toMatchSnapshot('leet submodels')
77 | })
78 |
79 | test('should parse meshes', () => {
80 | const bodyParts = ModelParser.parseBodyParts(dataView, header.bodyPartIndex, header.numBodyParts)
81 | const subModels = ModelParser.parseSubModel(dataView, bodyParts)
82 | const meshes = ModelParser.parseMeshes(dataView, subModels)
83 |
84 | expect(meshes).toMatchSnapshot('leet meshes')
85 | })
86 |
87 | test('should parse vertices', () => {
88 | const bodyParts = ModelParser.parseBodyParts(dataView, header.bodyPartIndex, header.numBodyParts)
89 | const subModels = ModelParser.parseSubModel(dataView, bodyParts)
90 | const vertices = ModelParser.parseVertices(dataView.buffer, subModels)
91 |
92 | expect(vertices).toMatchSnapshot('leet vertices')
93 | })
94 |
95 | test('should parse bones vertices', () => {
96 | const bodyParts = ModelParser.parseBodyParts(dataView, header.bodyPartIndex, header.numBodyParts)
97 | const subModels = ModelParser.parseSubModel(dataView, bodyParts)
98 | const vertBoneBuffer = ModelParser.parseVertBoneBuffer(dataView.buffer, subModels)
99 |
100 | expect(vertBoneBuffer).toMatchSnapshot('leet bone vertices')
101 | })
102 |
103 | test('should parse triangles', () => {
104 | const bodyParts = ModelParser.parseBodyParts(dataView, header.bodyPartIndex, header.numBodyParts)
105 | const subModels = ModelParser.parseSubModel(dataView, bodyParts)
106 | const meshes = ModelParser.parseMeshes(dataView, subModels)
107 | const triangles = ModelParser.parseTriangles(dataView.buffer, meshes, header.length)
108 |
109 | expect(triangles).toMatchSnapshot('leet triangles')
110 | })
111 |
112 | test('should parse animations', () => {
113 | const sequences = ModelParser.parseSequences(dataView, header.seqIndex, header.numSeq)
114 | const animations = ModelParser.parseAnimations(dataView, sequences, header.numBones)
115 |
116 | expect(animations).toMatchSnapshot('leet animation')
117 | })
118 |
119 | test('should parse animation values', () => {
120 | const sequences = ModelParser.parseSequences(dataView, header.seqIndex, header.numSeq)
121 | const animations = ModelParser.parseAnimations(dataView, sequences, header.numBones)
122 |
123 | const animValues = ModelParser.parseAnimValues(dataView, sequences, animations, header.numBones)
124 | const slicedAnimValues1 = (animValues.array as Int16Array).slice(0, 1000)
125 | const slicedAnimValues2 = (animValues.array as Int16Array).slice(10000, 11000)
126 |
127 | expect(slicedAnimValues1).toMatchSnapshot('leet animation values from 0 to 1000')
128 | expect(slicedAnimValues2).toMatchSnapshot('leet animation values from 10 000 to 11 000')
129 | })
130 | })
131 |
132 | describe('parsing whole model', () => {
133 | test('just parsing without errors', () => {
134 | expect(() => {
135 | ModelParser.parseModel(leetBuffer)
136 | }).not.toThrowError()
137 | })
138 |
139 | test('should have all keys in model data', () => {
140 | const expected = [
141 | 'header',
142 | 'bones',
143 | 'boneControllers',
144 | 'attachments',
145 | 'hitBoxes',
146 | 'sequences',
147 | 'sequenceGroups',
148 | 'bodyParts',
149 | 'textures',
150 | 'skinRef',
151 | 'subModels',
152 | 'meshes',
153 | 'vertices',
154 | 'vertBoneBuffer',
155 | 'triangles',
156 | 'animations',
157 | 'animValues'
158 | ]
159 |
160 | const modelData: ModelParser.ModelData = ModelParser.parseModel(leetBuffer)
161 | expect(intersection(Object.keys(modelData), expected)).toEqual(expected)
162 | })
163 |
164 | test('should detect invalid version of model', () => {
165 | expect(() => {
166 | ModelParser.parseModel(ratamahattaBuffer)
167 | }).toThrowError('Unsupported version of the MDL file')
168 | })
169 | })
170 |
--------------------------------------------------------------------------------
/lib/__tests__/textureRenderer.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs'
2 | import * as path from 'path'
3 | import * as png from 'fast-png'
4 | import { toMatchImageSnapshot } from 'jest-image-snapshot'
5 | import { parseModel } from '../modelDataParser'
6 | import { buildTexture } from '../textureBuilder'
7 |
8 | // Extending jest's expect
9 | expect.extend({ toMatchImageSnapshot })
10 |
11 | // Mock of ImageData
12 | Object.defineProperty(global, 'ImageData', {
13 | value: class MockImageData {
14 | constructor(public data: Uint8ClampedArray, public width: number, public height: number) {}
15 | }
16 | })
17 |
18 | const leetPath = path.resolve(__dirname, '../../__mock__/leet.mdl')
19 | const leetBuffer: ArrayBuffer = fs.readFileSync(leetPath).buffer
20 | const leetModelData = parseModel(leetBuffer)
21 |
22 | describe('test textures building', () => {
23 | test('just building texture without errors', () => {
24 | const texture = leetModelData.textures[0]
25 | buildTexture(leetBuffer, texture)
26 | })
27 |
28 | test('should build valid skin texture', () => {
29 | const texture = leetModelData.textures[0]
30 | const buildedSkin: Uint8Array = png.encode({
31 | width: texture.width,
32 | height: texture.height,
33 | data: buildTexture(leetBuffer, texture)
34 | })
35 |
36 | expect(Buffer.from(buildedSkin)).toMatchImageSnapshot()
37 | })
38 |
39 | test('should build valid backpack texture', () => {
40 | const texture = leetModelData.textures[2]
41 | const buildedSkin: Uint8Array = png.encode({
42 | width: texture.width,
43 | height: texture.height,
44 | data: buildTexture(leetBuffer, texture)
45 | })
46 |
47 | expect(Buffer.from(buildedSkin)).toMatchImageSnapshot()
48 | })
49 | })
50 |
--------------------------------------------------------------------------------
/lib/binaryReader.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-unused-vars
2 | import { Struct, StructResult } from './dataTypes' // FIXME
3 |
4 | /**
5 | * Parses binary buffer (wrapped to DataView) according to a structure
6 | * description
7 | * @param dataView The DataView object
8 | * @param struct Structure description. The structure can't contain keys
9 | * starting with a digit due to the peculiarities of the javascript engine.
10 | * Otherwise, the reading result may be corrupted.
11 | * @param byteOffset Offset in buffer to read, "0" by default
12 | * @returns The structure applying result
13 | */
14 | export const readStruct = function (
15 | dataView: DataView,
16 | struct: S,
17 | byteOffset: number = 0
18 | ): StructResult {
19 | let offset: number = byteOffset
20 | const structResult = {} as StructResult
21 |
22 | for (const key in struct) {
23 | ;(structResult as any)[key] = struct[key].getValue(dataView, offset)
24 |
25 | offset += struct[key].byteLength
26 | }
27 |
28 | return structResult
29 | }
30 |
31 | /**
32 | * @todo Describe me
33 | *
34 | * @param dataView The DataView object
35 | * @param struct Structure description. The structure can't contain keys
36 | * starting with a digit due to the peculiarities of the javascript engine.
37 | * Otherwise, the reading result may be corrupted.
38 | * @param byteOffset Offset in buffer to read, "0" by default
39 | * @param times Times of structure repeating
40 | * @returns The array of structure applying result
41 | */
42 | export const readStructMultiple = function (
43 | dataView: DataView,
44 | struct: S,
45 | byteOffset: number = 0,
46 | times: number = 1
47 | ): StructResult[] {
48 | let offset: number = byteOffset
49 | const result: StructResult[] = []
50 |
51 | for (let i = 0; i < times; i++) {
52 | const structResult = {} as StructResult
53 |
54 | for (const key in struct) {
55 | ;(structResult as any)[key] = struct[key].getValue(dataView, offset)
56 |
57 | offset += struct[key].byteLength
58 | }
59 |
60 | result[i] = structResult
61 | }
62 |
63 | return result
64 | }
65 |
66 | /**
67 | * Returns length of a structure
68 | * @param struct Structure description
69 | */
70 | export const getStructLength = (struct: S): number => {
71 | let length: number = 0
72 |
73 | for (const key in struct) {
74 | length += struct[key].byteLength
75 | }
76 |
77 | return length
78 | }
79 |
--------------------------------------------------------------------------------
/lib/dataTypes.ts:
--------------------------------------------------------------------------------
1 | /** Type of typed array instance */
2 | // eslint-disable-next-line no-unused-vars
3 | declare type TypedArray
4 | = | Int8Array
5 | | Uint8Array
6 | | Int16Array
7 | | Uint16Array
8 | | Int32Array
9 | | Uint32Array
10 | | Float32Array
11 | | Float64Array
12 |
13 | /**
14 | * Type of struct data type description
15 | */
16 | // eslint-disable-next-line space-infix-ops
17 | export type DataType = {
18 | /**
19 | * Byte length of the data type
20 | */
21 | byteLength: number
22 |
23 | /**
24 | * Value getter
25 | * @param dataView The DataView object
26 | * @param byteOffset Offset from the start of the DataView object
27 | * @param byteLength
28 | */
29 | getValue(dataView: DataView, byteOffset: number, byteLength?: number): T
30 |
31 | /**
32 | * Constructor of array of the values. Required to create a typed array of
33 | * values of this type to increase performance.
34 | */
35 | arrayConstructor?: A extends T[] ? never : { new (length: number): A }
36 | }
37 |
38 | /**
39 | * Type of description of struct
40 | */
41 | // eslint-disable-next-line space-infix-ops
42 | export type Struct = {
43 | [entry: string]: DataType
44 | }
45 |
46 | /**
47 | * Returns type of structure
48 | */
49 | // eslint-disable-next-line space-infix-ops
50 | export type StructResult> = { [K in keyof T]: ReturnType }
51 |
52 | /**
53 | * Creates reader of the Int8 value at the specified byte offset from the
54 | * start of the DataView object. It has a minimum value of -128 and a
55 | * maximum value of 127 (inclusive).
56 | */
57 | export const int8: DataType = {
58 | byteLength: 1,
59 | getValue: (dataView, offset) => dataView.getInt8(offset),
60 | arrayConstructor: Int8Array
61 | }
62 |
63 | /**
64 | * Alias for int8. Creates reader of the Int8 value at the specified byte
65 | * offset from the start of the DataView object. It has a minimum value of
66 | * -128 and a maximum value of 127 (inclusive).
67 | */
68 | export const byte = int8
69 |
70 | /**
71 | * Creates reader of the Uint8 value at the specified byte offset from the
72 | * start of the DataView object. It has a minimum value of 0 and a
73 | * maximum value of 255 (inclusive).
74 | */
75 | export const uint8: DataType = {
76 | byteLength: 1,
77 | getValue: (dataView, offset) => dataView.getUint8(offset),
78 | arrayConstructor: Uint8Array
79 | }
80 |
81 | /**
82 | * Alias for uint8. Creates reader of the Uint8 value at the specified byte
83 | * offset from the start of the DataView object. It has a minimum value of 0
84 | * and a maximum value of 255 (inclusive).
85 | */
86 | export const ubyte = uint8
87 |
88 | /**
89 | * Creates reader of the Int16 value at the specified byte offset from the
90 | * start of the DataView object. It has a minimum value of -32 768 and a
91 | * maximum value of 32 767 (inclusive).
92 | */
93 | export const int16: DataType = {
94 | byteLength: 2,
95 | getValue: (dataView, offset) => dataView.getInt16(offset, true),
96 | arrayConstructor: Int16Array
97 | }
98 |
99 | /**
100 | * Alias for uint16. Creates reader of the Int16 value at the specified byte
101 | * offset from the start of the DataView object. It has a minimum value of
102 | * -32 768 and a maximum value of 32 767 (inclusive).
103 | */
104 | export const short = int16
105 |
106 | /**
107 | * Creates reader of the Uint16 value at the specified byte offset from the
108 | * start of the DataView object.
109 | */
110 | export const uint16: DataType = {
111 | byteLength: 2,
112 | getValue: (dataView, offset) => dataView.getUint16(offset, true),
113 | arrayConstructor: Uint16Array
114 | }
115 |
116 | /**
117 | * Alias for uint16. Creates reader of the Uint16 value at the specified byte
118 | * offset from the start of the DataView object.
119 | */
120 | export const ushort = uint16
121 |
122 | /**
123 | * Creates reader of the Int32 value at the specified byte offset from the
124 | * start of the DataView object.
125 | */
126 | export const int32: DataType = {
127 | byteLength: 4,
128 | getValue: (dataView, offset) => dataView.getInt32(offset, true),
129 | arrayConstructor: Int32Array
130 | }
131 |
132 | /**
133 | * Alias for int32. Creates reader of the Int32 value at the specified byte
134 | * offset from the start of the DataView object.
135 | */
136 | export const int = int32
137 |
138 | /**
139 | * Creates reader of the Uint32 value at the specified byte offset from the
140 | * start of the DataView object.
141 | */
142 | export const uint32: DataType = {
143 | byteLength: 4,
144 | getValue: (dataView, offset) => dataView.getUint32(offset, true),
145 | arrayConstructor: Uint32Array
146 | }
147 |
148 | /**
149 | * Alias for uint32. Creates reader of the Uint32 value at the specified byte
150 | * offset from the start of the DataView object.
151 | */
152 | export const uint = uint32
153 |
154 | /**
155 | * Creates reader of the Float32 value at the specified byte offset from the
156 | * start of the DataView object.
157 | */
158 | export const float32: DataType = {
159 | byteLength: 4,
160 | getValue: (dataView, offset) => dataView.getFloat32(offset, true),
161 | arrayConstructor: Float32Array
162 | }
163 |
164 | /**
165 | * Alias for float32. Creates reader of the Float32 value at the specified
166 | * byte offset from the start of the DataView object.
167 | */
168 | export const float = float32
169 |
170 | /**
171 | * Creates reader of the Float64 value at the specified byte offset from the
172 | * start of the DataView object.
173 | */
174 | export const float64: DataType = {
175 | byteLength: 8,
176 | getValue: (dataView, offset) => dataView.getFloat64(offset, true),
177 | arrayConstructor: Float64Array
178 | }
179 |
180 | /**
181 | * Alias for float64. Creates reader of the Float64 value at the specified
182 | * byte offset from the start of the DataView object.
183 | */
184 | export const double = float64
185 |
186 | /**
187 | * Creates reader of the String value at the specified byte offset from the
188 | * start of the DataView object.
189 | * @param length Length of the string
190 | */
191 | export const string = (length: number): DataType => ({
192 | byteLength: length,
193 | getValue(dataView, offset) {
194 | let string = ''
195 |
196 | for (let i = 0; i < length; i += uint8.byteLength) {
197 | const charCode: number = uint8.getValue(dataView, offset + i)
198 |
199 | // End of the string
200 | if (charCode === 0) {
201 | break
202 | }
203 |
204 | string += String.fromCharCode(charCode)
205 | }
206 |
207 | return string
208 | }
209 | })
210 |
211 | /**
212 | * Creates reader of the array of the specified type values at the specified
213 | * byte offset from the start of the DataView object.
214 | */
215 | export const array = (arrayLength: number, structType: DataType): DataType => ({
216 | byteLength: structType.byteLength * arrayLength,
217 | getValue(dataView, byteOffset): A {
218 | const TypedArrayConstructor = structType.arrayConstructor
219 | const out = (TypedArrayConstructor ? new TypedArrayConstructor(arrayLength) : Array(arrayLength)) as A
220 |
221 | for (let i = 0; i < arrayLength; i++) {
222 | const itemByteLength = i * structType.byteLength
223 | const value = structType.getValue(dataView, byteOffset + itemByteLength, structType.byteLength)
224 |
225 | out[i] = value
226 | }
227 |
228 | return out
229 | }
230 | })
231 |
232 | /**
233 | * Alias for float32-vector with 3 elements
234 | */
235 | export const vec3 = array(3, float)
236 |
237 | /**
238 | * Creates an element to skip a specified number of bytes or number of bytes in
239 | * accordance with the specified data type.
240 | * @param lengthOrDataType Length in bytes to skip or data type
241 | */
242 | export const skip = (lengthOrDataType: number | DataType): DataType => ({
243 | byteLength: typeof lengthOrDataType === 'number' ? lengthOrDataType : lengthOrDataType.byteLength,
244 | getValue() {}
245 | })
246 |
--------------------------------------------------------------------------------
/lib/geometryBuilder.ts:
--------------------------------------------------------------------------------
1 | import * as structs from '../const/structs'
2 | import { TRIANGLE_FAN, TRIANGLE_STRIP } from '../const/constants'
3 |
4 | /**
5 | * Returns type of a series of connected triangles
6 | */
7 | export const getTriangleSeriesType = (trianglesSeriesHead: number) =>
8 | trianglesSeriesHead < 0 ? TRIANGLE_FAN : TRIANGLE_STRIP
9 |
10 | /**
11 | * Counts vertices for building geometry buffer
12 | * @param trianglesBuffer Triangles buffer
13 | * @return Number of vertices
14 | */
15 | export const countVertices = (trianglesBuffer: Int16Array): number => {
16 | // Count of vertices
17 | let vertCount = 0
18 |
19 | // Current position in buffer
20 | let p = 0
21 |
22 | // Processing triangle series
23 | while (trianglesBuffer[p]) {
24 | // Number of following vertices
25 | const verticesNum = Math.abs(trianglesBuffer[p])
26 |
27 | // This position is no longer needed,
28 | // we proceed to the following
29 | p += verticesNum * 4 + 1
30 |
31 | // Increase the number of vertices
32 | vertCount += (verticesNum - 3) * 3 + 3
33 | }
34 |
35 | return vertCount
36 | }
37 |
38 | /**
39 | * Unpack faces of the mesh
40 | * @param trianglesBuffer Triangles data
41 | * @param verticesBuffer Vertices
42 | * @param texture Image data of texture
43 | * @returns
44 | *
45 | * @todo Make faster and simpler.
46 | */
47 | export const readFacesData = (trianglesBuffer: Int16Array, verticesBuffer: Float32Array, texture: structs.Texture) => {
48 | // Number of vertices for generating buffer
49 | const vertNumber = countVertices(trianglesBuffer)
50 |
51 | // List of vertices data: origin and uv position on texture
52 | const verticesData: number[][] = []
53 |
54 | // Current position in buffer
55 | let trisPos = 0
56 |
57 | // Processing triangle series
58 | while (trianglesBuffer[trisPos]) {
59 | // Detecting triangle series type
60 | const trianglesType = trianglesBuffer[trisPos] < 0 ? TRIANGLE_FAN : TRIANGLE_STRIP
61 |
62 | // Starting vertex for triangle fan
63 | let startVert: number[] | null = null
64 |
65 | // Number of following triangles
66 | const trianglesNum = Math.abs(trianglesBuffer[trisPos])
67 |
68 | // This index is no longer needed,
69 | // we proceed to the following
70 | trisPos++
71 |
72 | // For counting we will make steps for 4 array items:
73 | // 0 — index of the vertex origin in vertices buffer
74 | // 1 — light (?)
75 | // 2 — first uv coordinate
76 | // 3 — second uv coordinate
77 | for (let j = 0; j < trianglesNum; j++, trisPos += 4) {
78 | const vertIndex: number = trianglesBuffer[trisPos]
79 | const vert: number = trianglesBuffer[trisPos] * 3
80 | // const light: number = trianglesBuffer[trisPos + 1] // ?
81 |
82 | // Vertex data
83 | const vertexData = [
84 | // Origin
85 | verticesBuffer[vert + 0],
86 | verticesBuffer[vert + 1],
87 | verticesBuffer[vert + 2],
88 |
89 | // UV data
90 | trianglesBuffer[trisPos + 2] / texture.width,
91 | 1 - trianglesBuffer[trisPos + 3] / texture.height,
92 |
93 | // Vertex index for getting bone transforms in subsequent calculations
94 | vertIndex
95 | ]
96 |
97 | // Unpacking triangle strip. Each next vertex, beginning with the third,
98 | // forms a triangle with the last and the penultimate vertex.
99 | // 1 ________3 ________ 5
100 | // ╱╲ ╱╲ ╱╲
101 | // ╱ ╲ ╱ ╲ ╱ ╲
102 | // ╱________╲╱________╲╱________╲
103 | // 0 2 4 6
104 | if (trianglesType === TRIANGLE_STRIP) {
105 | if (j > 2) {
106 | if (j % 2 === 0) {
107 | // even
108 | verticesData.push(
109 | verticesData[verticesData.length - 3], // previously first one
110 | verticesData[verticesData.length - 1] // last one
111 | )
112 | } else {
113 | // odd
114 | verticesData.push(
115 | verticesData[verticesData.length - 1], // last one
116 | verticesData[verticesData.length - 2] // second to last
117 | )
118 | }
119 | }
120 | }
121 |
122 | // Unpacking triangle fan. Each next vertex, beginning with the third,
123 | // forms a triangle with the last and first vertex.
124 | // 2 ____3 ____ 4
125 | // ╱╲ | ╱╲
126 | // ╱ ╲ | ╱ ╲
127 | // ╱________╲|╱________╲
128 | // 1 0 5
129 | if (trianglesType === TRIANGLE_FAN) {
130 | startVert = startVert || vertexData
131 |
132 | if (j > 2) {
133 | verticesData.push(startVert, verticesData[verticesData.length - 1])
134 | }
135 | }
136 |
137 | // New one
138 | verticesData.push(vertexData)
139 | }
140 | }
141 |
142 | // Converting array of vertices data to geometry buffer and UV buffer
143 | const vertices = new Float32Array(vertNumber * 3)
144 | const uv = new Float32Array(vertNumber * 2)
145 | const indices = new Int16Array(vertNumber)
146 |
147 | for (let i = 0; i < vertNumber; i++) {
148 | vertices[i * 3 + 0] = verticesData[i][0]
149 | vertices[i * 3 + 1] = verticesData[i][1]
150 | vertices[i * 3 + 2] = verticesData[i][2]
151 |
152 | uv[i * 2 + 0] = verticesData[i][3]
153 | uv[i * 2 + 1] = verticesData[i][4]
154 |
155 | indices[i] = verticesData[i][5]
156 | }
157 |
158 | return {
159 | vertices,
160 | uv,
161 | indices
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/lib/geometryTransformer.ts:
--------------------------------------------------------------------------------
1 | import { vec3, quat, mat4 } from 'gl-matrix'
2 | import * as MultiArrayView from 'multi-array-view'
3 | import * as structs from '../const/structs'
4 | import { ANIM_VALUE, MOTION_X, MOTION_Z } from '../const/constants'
5 | import { ModelData } from './modelDataParser'
6 |
7 | /**
8 | * Converts Euler angles into a quaternion
9 | */
10 | export function anglesToQuaternion(angles: vec3): quat {
11 | const pitch = angles[0]
12 | const roll = angles[1]
13 | const yaw = angles[2]
14 |
15 | // FIXME: rescale the inputs to 1/2 angle
16 | const cy = Math.cos(yaw * 0.5)
17 | const sy = Math.sin(yaw * 0.5)
18 | const cp = Math.cos(roll * 0.5)
19 | const sp = Math.sin(roll * 0.5)
20 | const cr = Math.cos(pitch * 0.5)
21 | const sr = Math.sin(pitch * 0.5)
22 |
23 | return quat.fromValues(
24 | sr * cp * cy - cr * sp * sy, // X
25 | cr * sp * cy + sr * cp * sy, // Y
26 | cr * cp * sy - sr * sp * cy, // Z
27 | cr * cp * cy + sr * sp * sy // W
28 | )
29 | }
30 |
31 | /**
32 | * Calculates bone angle
33 | */
34 | export const calcBoneQuaternion = (
35 | frame: number,
36 | bone: structs.Bone,
37 | animOffset: Uint16Array,
38 | animValues: MultiArrayView,
39 | sequenceIndex: number,
40 | boneIndex: number,
41 | s: number
42 | ): quat => {
43 | const angle1: vec3 = vec3.create()
44 | const angle2: vec3 = vec3.create()
45 |
46 | for (let axis = 0; axis < 3; axis++) {
47 | if (animOffset[axis + 3] == 0) {
48 | angle2[axis] = angle1[axis] = bone.value[axis + 3] // default;
49 | } else {
50 | const getTotal = (index: number) => animValues.get(sequenceIndex, boneIndex, axis, index, ANIM_VALUE.TOTAL)
51 | const getValue = (index: number) => animValues.get(sequenceIndex, boneIndex, axis, index, ANIM_VALUE.VALUE)
52 | const getValid = (index: number) => animValues.get(sequenceIndex, boneIndex, axis, index, ANIM_VALUE.VALID)
53 |
54 | let i = 0
55 | let k = frame
56 |
57 | while (getTotal(i) <= k) {
58 | k -= getTotal(i)
59 | i += getValid(i) + 1
60 | }
61 |
62 | // Bah, missing blend!
63 | if (getValid(i) > k) {
64 | angle1[axis] = getValue(i + k + 1)
65 |
66 | if (getValid(i) > k + 1) {
67 | angle2[axis] = getValue(i + k + 2)
68 | } else {
69 | if (getTotal(i) > k + 1) {
70 | angle2[axis] = angle1[axis]
71 | } else {
72 | angle2[axis] = getValue(i + getValid(i) + 2)
73 | }
74 | }
75 | } else {
76 | angle1[axis] = getValue(i + getValid(i))
77 |
78 | if (getTotal(i) > k + 1) {
79 | angle2[axis] = angle1[axis]
80 | } else {
81 | angle2[axis] = getValue(i + getValid(i) + 2)
82 | }
83 | }
84 |
85 | angle1[axis] = bone.value[axis + 3] + angle1[axis] * bone.scale[axis + 3]
86 | angle2[axis] = bone.value[axis + 3] + angle2[axis] * bone.scale[axis + 3]
87 | }
88 | }
89 |
90 | if (vec3.equals(angle1, angle2)) {
91 | return anglesToQuaternion(angle1)
92 | }
93 |
94 | const q1 = anglesToQuaternion(angle1)
95 | const q2 = anglesToQuaternion(angle2)
96 |
97 | return quat.slerp(quat.create(), q1, q2, s)
98 | }
99 |
100 | /**
101 | * Returns bone positions
102 | */
103 | export const getBonePositions = (
104 | frame: number,
105 | bone: structs.Bone,
106 | animOffset: Uint16Array,
107 | animValues: MultiArrayView,
108 | sequenceIndex: number,
109 | boneIndex: number,
110 | // TODO: Do something about it
111 | s: number
112 | ): vec3 => {
113 | // List of bone positions
114 | const position: vec3 = vec3.create()
115 |
116 | for (let axis = 0; axis < 3; axis++) {
117 | position[axis] = bone.value[axis] // default;
118 |
119 | // TOD: fix this part
120 |
121 | // if (animOffset[axis] != 0) {
122 | // const getTotal = (index: number) => animValues.get(sequenceIndex, boneIndex, axis, index, ANIM_VALUE.TOTAL)
123 | // const getValue = (index: number) => animValues.get(sequenceIndex, boneIndex, axis, index, ANIM_VALUE.VALUE)
124 | // const getValid = (index: number) => animValues.get(sequenceIndex, boneIndex, axis, index, ANIM_VALUE.VALID)
125 |
126 | // let i = 0
127 | // let k = frame
128 |
129 | // // find span of values that includes the frame we want
130 | // while (getTotal(i) <= k) {
131 | // k -= getTotal(i)
132 | // i += getValid(i) + 1
133 | // }
134 |
135 | // // if we're inside the span
136 | // if (getValid(i) > k) {
137 | // // and there's more data in the span
138 | // if (getValid(i) > k + 1) {
139 | // position[axis] += (getValue(i + k + 1) * (1.0 - s) + s * getValue(i + k + 2)) * bone.scale[axis]
140 | // } else {
141 | // position[axis] += getValue(i + k + 1) * bone.scale[axis]
142 | // }
143 | // } else {
144 | // // are we at the end of the repeating values section and there's another section with data?
145 | // if (getTotal(i) <= k + 1) {
146 | // position[axis]
147 | // += (getValue(i + getValid(i)) * (1.0 - s) + s * getValue(i + getValid(i) + 2)) * bone.scale[axis]
148 | // } else {
149 | // position[axis] += getValue(i + getValid(i)) * bone.scale[axis]
150 | // }
151 | // }
152 | // }
153 | }
154 |
155 | return position
156 | }
157 |
158 | /**
159 | * Calculates bone transforms
160 | */
161 | export const calcBoneTransforms = (quaternions: quat[], positions: vec3[], bones: structs.Bone[]): mat4[] => {
162 | const boneTransforms: mat4[] = []
163 |
164 | for (let i = 0; i < bones.length; i++) {
165 | const boneMatrix = mat4.fromQuat(mat4.create(), quaternions[i])
166 |
167 | for (let j = 0; j < 3; j++) {
168 | // 4 — vector size, 3 + j — index in quat
169 | boneMatrix[4 * 3 + j] = positions[i][j]
170 | }
171 |
172 | if (bones[i].parent === -1) {
173 | // Root bone
174 | boneTransforms.push(boneMatrix)
175 | } else {
176 | boneTransforms.push(mat4.multiply(mat4.create(), boneTransforms[bones[i].parent], boneMatrix))
177 | }
178 | }
179 |
180 | return boneTransforms
181 | }
182 |
183 | export const calcRotations = (
184 | modelData: ModelData,
185 | sequenceIndex: number,
186 | frame: number,
187 | // TODO: Do something about it
188 | s = 0
189 | ): mat4[] => {
190 | const boneQuaternions: quat[] = modelData.bones.map(
191 | (_, boneIndex): quat =>
192 | calcBoneQuaternion(
193 | frame,
194 | modelData.bones[boneIndex],
195 | modelData.animations[sequenceIndex][boneIndex].offset,
196 | modelData.animValues,
197 | sequenceIndex,
198 | boneIndex,
199 | s
200 | )
201 | )
202 |
203 | const bonesPositions: vec3[] = modelData.bones.map(
204 | (_, boneIndex): vec3 =>
205 | getBonePositions(
206 | frame,
207 | modelData.bones[boneIndex],
208 | modelData.animations[sequenceIndex][boneIndex].offset,
209 | modelData.animValues,
210 | sequenceIndex,
211 | boneIndex,
212 | s
213 | )
214 | )
215 |
216 | for (const axis of [MOTION_X, MOTION_X, MOTION_Z]) {
217 | if (modelData.sequences[sequenceIndex].motionType & axis) {
218 | bonesPositions[modelData.sequences[sequenceIndex].motionBone][1] = 0
219 | }
220 | }
221 |
222 | return calcBoneTransforms(boneQuaternions, bonesPositions, modelData.bones)
223 | }
224 |
--------------------------------------------------------------------------------
/lib/modelController.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three'
2 | import * as R from 'ramda'
3 | import { ModelData } from './modelDataParser'
4 | import { MeshRenderData } from './modelRenderer'
5 |
6 | /**
7 | * Calculates frame index by animation time, fps and frames number
8 | */
9 | const getCurrentFrame = (time: number, fps: number, numFrames: number) =>
10 | Math.floor((time % (numFrames / fps)) / (1 / fps))
11 |
12 | /**
13 | * Returns the mesh's animation clips
14 | */
15 | export const prepareAnimationClips = (meshData: MeshRenderData, modelData: ModelData): THREE.AnimationClip[] =>
16 | meshData.geometryBuffers.map((sequenceBuffers, i) => {
17 | // Sequence level
18 | const sequence = modelData.sequences[i]
19 | const isNoLoop = false
20 | const frameBuffers = sequenceBuffers.map((bufferAttribute, i) => ({
21 | name: i.toString(),
22 | vertices: bufferAttribute.array
23 | }))
24 |
25 | return THREE.AnimationClip.CreateFromMorphTargetSequence(
26 | sequence.label,
27 | frameBuffers as any, // FIXME
28 | sequence.fps,
29 | isNoLoop
30 | )
31 | })
32 |
33 | /**
34 | * Creates mesh controller
35 | */
36 | export const createMeshController = (
37 | mesh: THREE.Mesh,
38 | meshRenderData: MeshRenderData,
39 | modelData: ModelData,
40 | isDefaultVisible: boolean = false
41 | ) => {
42 | // Set default visibility of the mesh
43 | mesh.visible = isDefaultVisible
44 |
45 | let activeAction: THREE.AnimationAction | undefined
46 | let previousAction: THREE.AnimationAction | undefined
47 |
48 | const framesSum = modelData.sequences.reduce((acc, item) => acc + item.numFrames, 0)
49 | mesh.morphTargetInfluences = Array(framesSum).fill(0)
50 |
51 | const animationClips: THREE.AnimationClip[] = prepareAnimationClips(meshRenderData, modelData)
52 | const mixer = new THREE.AnimationMixer(mesh)
53 |
54 | const actions = animationClips.map(actionClip => mixer.clipAction(actionClip, mesh))
55 |
56 | const meshModelController = {
57 | /** Return pause state of the mesh */
58 | get isPaused() {
59 | if (activeAction) {
60 | return activeAction.paused
61 | }
62 |
63 | return false
64 | },
65 |
66 | /**
67 | * Sets playback rate (animation speed)
68 | * @param rate
69 | */
70 | setPlaybackRate: (rate: number) => {
71 | mixer.timeScale = rate !== 0 ? 1 / rate : 0
72 | },
73 |
74 | /**
75 | * Sets visibility of the mesh
76 | */
77 | setVisibility: (isVisible: boolean) => {
78 | mesh.visible = isVisible
79 | },
80 |
81 | /**
82 | * Updates delta tile
83 | */
84 | update: (deltaTime: number) => mixer.update(deltaTime),
85 |
86 | /**
87 | * Sets animation to play
88 | */
89 | setAnimation: (sequenceIndex: number) => {
90 | const wasPaused = activeAction ? activeAction.paused : false
91 |
92 | previousAction = activeAction
93 | activeAction = actions[sequenceIndex]
94 |
95 | if (previousAction) {
96 | previousAction.stop()
97 | }
98 |
99 | // Update mesh morph targets
100 | const geometry = mesh.geometry
101 | if (geometry instanceof THREE.BufferGeometry) {
102 | // mesh.updateMorphTargets()
103 |
104 | geometry.morphAttributes.position = meshRenderData.geometryBuffers[sequenceIndex]
105 | }
106 |
107 | activeAction.reset().play()
108 | activeAction.paused = wasPaused
109 | },
110 |
111 | /**
112 | * Set pause state of the running animation
113 | */
114 | setPause: (isPaused: boolean) => {
115 | if (activeAction) {
116 | activeAction.paused = isPaused
117 | }
118 | },
119 |
120 | /**
121 | * Jump to specific time of the running animation
122 | */
123 | setTime: (time: number) => {
124 | if (activeAction) {
125 | activeAction.time = time
126 | }
127 | },
128 |
129 | /**
130 | * Returns current time of the running animation
131 | */
132 | getCurrentTime: () => (activeAction ? activeAction.time : 0)
133 | }
134 |
135 | return meshModelController
136 | }
137 |
138 | /**
139 | * The model state
140 | */
141 | export type ModelState = {
142 | isPaused: boolean
143 | activeAnimationIndex: number
144 | showedSubModels: number[]
145 | frame: number
146 | playbackRate: number
147 | }
148 |
149 | /**
150 | * Creates model controller.
151 | * @todo refactor this shit
152 | */
153 | export const createModelController = (
154 | meshes: THREE.Mesh[][][],
155 | meshesRenderData: MeshRenderData[][][],
156 | modelData: ModelData,
157 | initialSequence: number = 0
158 | ) => {
159 | let playbackRate = 1
160 | let isAnimationPaused = false
161 |
162 | // Active sequence
163 | let activeSequenceIndex: number = initialSequence
164 |
165 | // List of showed sub models indices
166 | let showedSubModels: number[] = modelData.bodyParts.map(() => 0)
167 |
168 | // Path: [bodyPartIndex][subModelIndex][meshIndex][sequenceIndex]
169 | const meshControllers = meshes.map((bodyPart, bodyPartIndex) =>
170 | bodyPart.map((subModel, subModelIndex) =>
171 | subModel.map((mesh, meshIndex) =>
172 | createMeshController(
173 | mesh,
174 | meshesRenderData[bodyPartIndex][subModelIndex][meshIndex],
175 | modelData,
176 | subModelIndex === 0
177 | )
178 | )
179 | )
180 | )
181 |
182 | // Setting default animation
183 | meshControllers.forEach(bodyPart =>
184 | bodyPart.forEach(subModel => subModel.forEach(controller => controller.setAnimation(activeSequenceIndex)))
185 | )
186 |
187 | const getAnimationTime = () =>
188 | R.converge(R.divide, [R.sum, R.length])(
189 | meshControllers.reduce(
190 | (acc, bodyPart) =>
191 | acc.concat(
192 | bodyPart.reduce(
193 | (acc, subModel) => acc.concat(subModel.map(controller => controller.getCurrentTime())),
194 | [] as number[]
195 | )
196 | ),
197 | [] as number[]
198 | )
199 | )
200 |
201 | const areSubModelsPaused = () =>
202 | meshControllers.reduce(
203 | (acc, bodyPart) =>
204 | acc
205 | && bodyPart.reduce(
206 | (acc, subModel) => acc && subModel.reduce((acc, controller) => acc && controller.isPaused, true),
207 | true
208 | ),
209 | true
210 | )
211 |
212 | /** Returns current state of the model */
213 | const getCurrentState = (): ModelState => ({
214 | isPaused: areSubModelsPaused(),
215 | activeAnimationIndex: activeSequenceIndex,
216 | showedSubModels,
217 | frame: getCurrentFrame(
218 | getAnimationTime(),
219 | modelData.sequences[activeSequenceIndex].fps,
220 | modelData.sequences[activeSequenceIndex].numFrames
221 | ),
222 | playbackRate
223 | })
224 |
225 | const modelController = {
226 | /**
227 | * Updates delta til=me
228 | * @param deltaTime
229 | */
230 | update: (deltaTime: number) =>
231 | meshControllers.forEach(bodyPart =>
232 | bodyPart.forEach(subModel =>
233 | subModel.forEach(controller => {
234 | controller.update(deltaTime)
235 | })
236 | )
237 | ),
238 |
239 | /** Returns current state of the model */
240 | getCurrentState,
241 |
242 | /** Set pause state of the model */
243 | setPause: (isPaused: boolean) => {
244 | isAnimationPaused = isPaused
245 |
246 | meshControllers.forEach(bodyPart =>
247 | bodyPart.forEach(subModel => subModel.forEach(controller => controller.setPause(isAnimationPaused)))
248 | )
249 |
250 | return getCurrentState()
251 | },
252 |
253 | /**
254 | * Sets playback rate (animation speed)
255 | * @param rate
256 | */
257 | setPlaybackRate: (rate: number) => {
258 | playbackRate = rate
259 |
260 | meshControllers.forEach(bodyPart =>
261 | bodyPart.forEach(subModel => subModel.forEach(controller => controller.setPlaybackRate(playbackRate)))
262 | )
263 |
264 | return getCurrentState()
265 | },
266 | /**
267 | * Sets animation to play
268 | * @param sequenceIndex
269 | */
270 | setAnimation: (sequenceIndex: number) => {
271 | activeSequenceIndex = sequenceIndex
272 |
273 | meshControllers.forEach(bodyPart =>
274 | bodyPart.forEach(subModel => subModel.forEach(controller => controller.setAnimation(sequenceIndex)))
275 | )
276 |
277 | return getCurrentState()
278 | },
279 |
280 | /**
281 | * Shows specified sub model
282 | */
283 | showSubModel: (bodyPartIndex: number, subModelIndex: number) => {
284 | showedSubModels[bodyPartIndex] = subModelIndex
285 |
286 | meshControllers[bodyPartIndex].forEach((subModel, i) => {
287 | const isVisible = i === subModelIndex
288 |
289 | subModel.forEach(controller => {
290 | controller.setVisibility(isVisible)
291 | })
292 | })
293 |
294 | return getCurrentState()
295 | },
296 |
297 | /**
298 | * Sets specific frame of the running animations
299 | */
300 | setFrame: (frame: number) => {
301 | const { numFrames, fps } = modelData.sequences[activeSequenceIndex]
302 | const safeFrame = R.clamp(0, numFrames, frame)
303 | const duration = numFrames / fps
304 | const specifiedTime = (duration / numFrames) * safeFrame
305 |
306 | meshControllers.forEach(bodyPart =>
307 | bodyPart.forEach(subModel => subModel.forEach(controller => controller.setTime(specifiedTime)))
308 | )
309 |
310 | return getCurrentState()
311 | }
312 | }
313 |
314 | return modelController
315 | }
316 |
317 | export type ModelController = ReturnType
318 |
--------------------------------------------------------------------------------
/lib/modelDataParser.ts:
--------------------------------------------------------------------------------
1 | import * as FastDataView from 'fast-dataview'
2 | import * as MultiArrayView from 'multi-array-view'
3 | import * as structs from '../const/structs'
4 | import { MAX_SRCBONES, AXLES_NUM, ANIM_VALUE, VERSION } from '../const/constants'
5 | import * as BinaryReader from './binaryReader'
6 | import { Struct, StructResult, short, ubyte } from './dataTypes'
7 |
8 | /**
9 | * Creates multiple reader
10 | * @internal
11 | */
12 | const createMultipleParser = (struct: S) => (
13 | dataView: DataView,
14 | offsetIndex: number,
15 | number: number
16 | ): StructResult[] => BinaryReader.readStructMultiple(dataView, struct, offsetIndex, number)
17 |
18 | /** Parses header of the MDL file */
19 | export const parseHeader = (dataView: DataView): structs.Header => BinaryReader.readStruct(dataView, structs.header)
20 |
21 | /** Parses bones */
22 | export const parseBones = createMultipleParser(structs.bone)
23 |
24 | /** Parses bone controllers */
25 | export const parseBoneControllers = createMultipleParser(structs.boneController)
26 |
27 | /** Parses attachments */
28 | export const parseAttachments = createMultipleParser(structs.attachment)
29 |
30 | /** Parses bounding boxes */
31 | export const parseHitboxes = createMultipleParser(structs.boundingBox)
32 |
33 | /** Parses sequences */
34 | export const parseSequences = createMultipleParser(structs.seqDesc)
35 |
36 | /** Parses sequence groups */
37 | export const parseSequenceGroups = createMultipleParser(structs.seqGroup)
38 |
39 | /** Parses body parts */
40 | export const parseBodyParts = createMultipleParser(structs.bodyPart)
41 |
42 | /** Parses textures info */
43 | export const parseTextures = createMultipleParser(structs.texture)
44 |
45 | /** Parses skin references */
46 | export const parseSkinRef = (buffer: ArrayBuffer, skinRefOffset: number, numSkinRef: number) =>
47 | new Int16Array(buffer, skinRefOffset, numSkinRef)
48 |
49 | /**
50 | * Parses sub model
51 | * @todo make shorter
52 | */
53 | export const parseSubModel = (dataView: DataView, bodyParts: structs.BodyPart[]): structs.SubModel[][] =>
54 | bodyParts.map(bodyPart =>
55 | BinaryReader.readStructMultiple(dataView, structs.subModel, bodyPart.modelIndex, bodyPart.numModels)
56 | )
57 |
58 | /**
59 | * Parses meshes
60 | * @todo make shorter
61 | */
62 | export const parseMeshes = (dataView: DataView, subModels: structs.SubModel[][]): structs.Mesh[][][] =>
63 | subModels.map(bodyPart =>
64 | bodyPart.map(subModel =>
65 | BinaryReader.readStructMultiple(dataView, structs.mesh, subModel.meshIndex, subModel.numMesh)
66 | )
67 | )
68 |
69 | /**
70 | * Parses submodels vertices.
71 | * Path: vertices[bodyPartIndex][subModelIndex]
72 | */
73 | export const parseVertices = (buffer: ArrayBuffer, subModels: structs.SubModel[][]): Float32Array[][] => {
74 | return subModels.map(bodyPart =>
75 | bodyPart.map(subModel => new Float32Array(buffer, subModel.vertIndex, subModel.numVerts * 3))
76 | )
77 | }
78 |
79 | /**
80 | * Parses ones vertices buffer.
81 | * Path: vertBoneBuffer[bodyPartIndex][subModelIndex]
82 | */
83 | export const parseVertBoneBuffer = (buffer: ArrayBuffer, subModels: structs.SubModel[][]): Uint8Array[][] =>
84 | subModels.map(bodyPart => bodyPart.map(subModel => new Uint8Array(buffer, subModel.vertInfoIndex, subModel.numVerts)))
85 |
86 | /**
87 | * Parses meshes triangles.
88 | * Path: meshes[bodyPartIndex][subModelIndex][meshIndex]
89 | */
90 | export const parseTriangles = (
91 | buffer: ArrayBuffer,
92 | meshes: structs.Mesh[][][],
93 | headerLength: number
94 | ): Int16Array[][][] =>
95 | meshes.map(bodyPart =>
96 | bodyPart.map(subModel =>
97 | subModel.map(mesh => new Int16Array(buffer, mesh.triIndex, Math.floor((headerLength - mesh.triIndex) / 2)))
98 | )
99 | )
100 |
101 | /**
102 | * Parses bone animations
103 | * @todo make shorter
104 | */
105 | export const parseAnimations = (
106 | dataView: DataView,
107 | sequences: structs.SequenceDesc[],
108 | numBones: number
109 | ): structs.Animation[][] =>
110 | sequences.map(sequence => BinaryReader.readStructMultiple(dataView, structs.animation, sequence.animIndex, numBones))
111 |
112 | /**
113 | * Parses animation values
114 | */
115 | export const parseAnimValues = (
116 | dataView: DataView,
117 | sequences: structs.SequenceDesc[],
118 | animations: structs.Animation[][],
119 | numBones: number
120 | ): MultiArrayView => {
121 | const animStructLength = BinaryReader.getStructLength(structs.animation)
122 |
123 | // Create frames values array
124 | const animValues = MultiArrayView.create([sequences.length, numBones, AXLES_NUM, MAX_SRCBONES, 3], Int16Array)
125 |
126 | for (let i = 0; i < sequences.length; i++) {
127 | for (let j = 0; j < numBones; j++) {
128 | const animationIndex = /* seqGroup.data + */ sequences[i].animIndex + j * animStructLength
129 |
130 | for (let axis = 0; axis < AXLES_NUM; axis++) {
131 | for (let v = 0; v < MAX_SRCBONES; v++) {
132 | const offset = animationIndex + animations[i][j].offset[axis + AXLES_NUM] + v * short.byteLength
133 |
134 | // Using the "method" instead of applying a structure is an optimization of reading
135 | const value = short.getValue(dataView, offset)
136 | const valid = ubyte.getValue(dataView, offset)
137 | const total = ubyte.getValue(dataView, offset + ubyte.byteLength)
138 |
139 | animValues.set(value, i, j, axis, v, ANIM_VALUE.VALUE)
140 | animValues.set(valid, i, j, axis, v, ANIM_VALUE.VALID)
141 | animValues.set(total, i, j, axis, v, ANIM_VALUE.TOTAL)
142 | }
143 | }
144 | }
145 | }
146 |
147 | return animValues
148 | }
149 |
150 | /**
151 | * Returns parsed data of MDL file. A MDL file is a binary buffer divided in
152 | * two part: header and data. Information about the data and their position is
153 | * in the header.
154 | * @param modelBuffer The MDL file buffer
155 | * @returns {ModelDataParser}
156 | */
157 | export const parseModel = (modelBuffer: ArrayBuffer) => {
158 | // Create the DataView object from buffer of a MDL file for parsing
159 | const dataView = new FastDataView(modelBuffer)
160 |
161 | // Reading header of the model
162 | const header = parseHeader(dataView)
163 |
164 | // Checking version of MDL file
165 | if (header.version !== VERSION) {
166 | throw new Error('Unsupported version of the MDL file')
167 | }
168 |
169 | // Checking textures of the model
170 | // TODO: Handle model without textures
171 | if (!header.textureIndex || !header.numTextures) {
172 | throw new Error('No textures in the MDL file')
173 | }
174 |
175 | /// The data below will be used to obtain another data
176 |
177 | // Body parts info
178 | const bodyParts: structs.BodyPart[] = parseBodyParts(dataView, header.bodyPartIndex, header.numBodyParts)
179 | // Submodels info
180 | const subModels: structs.SubModel[][] = parseSubModel(dataView, bodyParts)
181 | // Meshes info
182 | const meshes = parseMeshes(dataView, subModels)
183 |
184 | // Model sequences info
185 | const sequences = parseSequences(dataView, header.seqIndex, header.numSeq)
186 | // Bones animations
187 | const animations = parseAnimations(dataView, sequences, header.numBones)
188 | // Animation frames
189 | const animValues = parseAnimValues(dataView, sequences, animations, header.numBones)
190 |
191 | const modelData = {
192 | /** The header of the MDL file */
193 | header,
194 |
195 | // Main data that was obtained directly from the MDL file header
196 |
197 | /** Bones info */
198 | bones: parseBones(dataView, header.boneIndex, header.numBones),
199 | /** Bone controllers */
200 | boneControllers: parseBoneControllers(dataView, header.boneControllerIndex, header.numBoneControllers),
201 | /** Model attachments */
202 | attachments: parseAttachments(dataView, header.attachmentIndex, header.numAttachments),
203 | /** Model hitboxes */
204 | hitBoxes: parseHitboxes(dataView, header.hitBoxIndex, header.numHitboxes),
205 | /** Model sequences info */
206 | sequences,
207 | /** Sequences groups */
208 | sequenceGroups: parseSequenceGroups(dataView, header.seqGroupIndex, header.numSeqGroups),
209 | /** Body parts info */
210 | bodyParts,
211 | /** Textures info */
212 | textures: parseTextures(dataView, header.textureIndex, header.numTextures),
213 | /** Skins references */
214 | skinRef: parseSkinRef(dataView.buffer, header.skinIndex, header.numSkinRef),
215 |
216 | // Sub models data. This data was obtained by parsing data from body parts
217 |
218 | /** Submodels info */
219 | subModels,
220 | /** Meshes info. Path: meshes[bodyPartIndex][subModelIndex][meshIndex] */
221 | meshes,
222 | /** Submodels vertices. Path: vertices[bodyPartIndex][subModelIndex] */
223 | vertices: parseVertices(dataView.buffer, subModels),
224 | /** Bones vertices buffer. Path: vertBoneBuffer[bodyPartIndex][subModelIndex] */
225 | vertBoneBuffer: parseVertBoneBuffer(dataView.buffer, subModels),
226 | /** Mesh triangles. Path: meshes[bodyPartIndex][subModelIndex][meshIndex] */
227 | triangles: parseTriangles(dataView.buffer, meshes, header.length),
228 |
229 | // Sequences data
230 |
231 | /** Bones animations */
232 | animations,
233 | /** Animation frames */
234 | animValues
235 | }
236 |
237 | return modelData
238 | }
239 |
240 | /**
241 | * Type of model parsing result
242 | */
243 | export type ModelData = ReturnType
244 |
--------------------------------------------------------------------------------
/lib/modelRenderer.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three'
2 | import { mat4 } from 'gl-matrix'
3 | import { ModelData } from './modelDataParser'
4 | import { readFacesData } from './geometryBuilder'
5 | import { calcRotations } from './geometryTransformer'
6 |
7 | /**
8 | * Mesh buffers of each frame of each sequence of the model and mesh UV-maps
9 | */
10 | export type MeshRenderData = {
11 | geometryBuffers: THREE.BufferAttribute[][]
12 | uvMap: THREE.BufferAttribute
13 | }
14 |
15 | /**
16 | * Creates THREE.Texture instance with presets
17 | */
18 | export const createTexture = (skinBuffer: Uint8ClampedArray, width: number, height: number): THREE.Texture => {
19 | const imageData = new ImageData(skinBuffer, width, height)
20 |
21 | const texture = new THREE.Texture(
22 | imageData as any,
23 | THREE.UVMapping,
24 | THREE.ClampToEdgeWrapping,
25 | THREE.ClampToEdgeWrapping,
26 | THREE.LinearFilter,
27 | THREE.LinearFilter,
28 | THREE.RGBAFormat,
29 | THREE.UnsignedByteType
30 | )
31 |
32 | texture.needsUpdate = true
33 |
34 | return texture
35 | }
36 |
37 | /**
38 | * Applies bone transforms to a position array and returns it
39 | */
40 | export const applyBoneTransforms = (
41 | vertices: Float32Array,
42 | vertIndices: Int16Array,
43 | vertBoneBuffer: Uint8Array,
44 | boneTransforms: mat4[]
45 | ): Float32Array => {
46 | const posArray = new Float32Array(vertices.length)
47 | for (let i = 0; i < vertIndices.length; i++) {
48 | const transform: mat4 = boneTransforms[vertBoneBuffer[vertIndices[i]]]
49 |
50 | // The vec3.transformMat4 function was removed from here, because its use
51 | // (creation of an additional vector) increased the code performance by
52 | // 4 times. Instead, it uses manual multiplication.
53 |
54 | const x = vertices[i * 3 + 0]
55 | const y = vertices[i * 3 + 1]
56 | const z = vertices[i * 3 + 2]
57 | const w = transform[3] * x + transform[7] * y + transform[11] * z + transform[15] || 1.0
58 |
59 | posArray[i * 3 + 0] = (transform[0] * x + transform[4] * y + transform[8] * z + transform[12]) / w
60 | posArray[i * 3 + 1] = (transform[1] * x + transform[5] * y + transform[9] * z + transform[13]) / w
61 | posArray[i * 3 + 2] = (transform[2] * x + transform[6] * y + transform[10] * z + transform[14]) / w
62 | }
63 |
64 | return posArray
65 | }
66 |
67 | /**
68 | * Returns generated mesh buffers and UV-maps of each frame of each sequence of
69 | * the model
70 | * @param modelData Model data
71 | */
72 | export const prepareRenderData = (modelData: ModelData): MeshRenderData[][][] => {
73 | const renderData: MeshRenderData[][][] = []
74 |
75 | for (let bodyPartIndex = 0; bodyPartIndex < modelData.bodyParts.length; bodyPartIndex++) {
76 | renderData[bodyPartIndex] = []
77 |
78 | for (let subModelIndex = 0; subModelIndex < modelData.subModels[bodyPartIndex].length; subModelIndex++) {
79 | renderData[bodyPartIndex][subModelIndex] = []
80 |
81 | for (let meshIndex = 0; meshIndex < modelData.meshes[bodyPartIndex][subModelIndex].length; meshIndex++) {
82 | const textureIndex = modelData.skinRef[modelData.meshes[bodyPartIndex][subModelIndex][meshIndex].skinRef]
83 |
84 | // Unpack faces of the mesh
85 | const { vertices, uv, indices } = readFacesData(
86 | modelData.triangles[bodyPartIndex][subModelIndex][meshIndex],
87 | modelData.vertices[bodyPartIndex][subModelIndex],
88 | modelData.textures[textureIndex]
89 | )
90 |
91 | renderData[bodyPartIndex][subModelIndex].push({
92 | // UV-map of the mesh
93 | uvMap: new THREE.BufferAttribute(uv, 2),
94 |
95 | // List of mesh buffer for each frame of each sequence
96 | geometryBuffers: modelData.sequences.map((sequence, sequenceIndex) => {
97 | const bufferAttributes = []
98 |
99 | for (let frame = 0; frame < sequence.numFrames; frame++) {
100 | const boneTransforms = calcRotations(modelData, sequenceIndex, frame)
101 | const transformedVertices = applyBoneTransforms(
102 | vertices,
103 | indices,
104 | modelData.vertBoneBuffer[bodyPartIndex][subModelIndex],
105 | boneTransforms
106 | )
107 |
108 | bufferAttributes.push(new THREE.BufferAttribute(transformedVertices, 3))
109 | }
110 |
111 | return bufferAttributes
112 | })
113 | })
114 | }
115 | }
116 | }
117 |
118 | return renderData
119 | }
120 |
121 | /**
122 | * Creates model mesh
123 | */
124 | export const createMesh = (
125 | geometryBuffer: THREE.BufferAttribute,
126 | uvMap: THREE.BufferAttribute,
127 | texture: THREE.Texture
128 | ) => {
129 | // Mesh level
130 | const material = new THREE.MeshBasicMaterial({
131 | map: texture,
132 | side: THREE.DoubleSide,
133 | transparent: true,
134 | alphaTest: 0.5,
135 | morphTargets: true,
136 | skinning: true
137 | // color: 0xffffff
138 | })
139 |
140 | // Prepare geometry
141 | const geometry = new THREE.BufferGeometry()
142 | geometry.addAttribute('position', geometryBuffer)
143 | geometry.addAttribute('uv', uvMap)
144 |
145 | // Prepare mesh
146 | return new THREE.Mesh(geometry, material)
147 | }
148 |
149 | /**
150 | * Creates list of meshes of every submodel
151 | */
152 | export const createModelMeshes = (
153 | meshesRenderData: MeshRenderData[][][],
154 | modelData: ModelData,
155 | textureBuffers: Uint8ClampedArray[]
156 | ): THREE.Mesh[][][] => {
157 | const textures: THREE.Texture[] = textureBuffers.map((textureBuffer, textureIndex) => {
158 | const texture = new THREE.Texture(
159 | new ImageData(
160 | textureBuffer,
161 | modelData.textures[textureIndex].width,
162 | modelData.textures[textureIndex].height
163 | ) as any,
164 | THREE.UVMapping,
165 | THREE.ClampToEdgeWrapping,
166 | THREE.ClampToEdgeWrapping,
167 | THREE.LinearFilter,
168 | THREE.LinearFilter,
169 | THREE.RGBAFormat,
170 | THREE.UnsignedByteType
171 | )
172 |
173 | texture.needsUpdate = true
174 |
175 | return texture
176 | })
177 |
178 | const modelMeshes: THREE.Mesh[][][] = meshesRenderData.map((bodyPart, bodyPartIndex) =>
179 | // Body part level
180 | bodyPart.map((subModel, subModelIndex) =>
181 | // Sub model level
182 | subModel.map(({ geometryBuffers, uvMap }, meshIndex) => {
183 | const initialGeometryBuffer = geometryBuffers[0][0]
184 | const textureIndex = modelData.skinRef[modelData.meshes[bodyPartIndex][subModelIndex][meshIndex].skinRef]
185 | const texture = textures[textureIndex]
186 |
187 | return createMesh(initialGeometryBuffer, uvMap, texture)
188 | })
189 | )
190 | )
191 |
192 | return modelMeshes
193 | }
194 |
195 | /**
196 | * Creates THREE.js object to render
197 | */
198 | export const createContainer = (meshes: THREE.Mesh[][][]) => {
199 | const container = new THREE.Group()
200 |
201 | // Adding meshes to the container
202 | meshes.forEach(bodyPart =>
203 | // Body part level
204 | bodyPart.forEach(subModel =>
205 | // Sub model level
206 | subModel.forEach(mesh => {
207 | // Mesh level
208 | container.add(mesh)
209 | })
210 | )
211 | )
212 |
213 | // Sets to display the front of the model
214 | container.rotation.x = THREE.Math.degToRad(-90)
215 | container.rotation.z = THREE.Math.degToRad(-90)
216 |
217 | // Sets to display model on the center of camera
218 | const boundingBox = new THREE.Box3().setFromObject(container)
219 | container.position.y = (boundingBox.min.y - boundingBox.max.y) / 2
220 |
221 | return container
222 | }
223 |
--------------------------------------------------------------------------------
/lib/screneRenderer.ts:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three'
2 | import * as orbitControlsCreator from 'three-orbit-controls'
3 |
4 | /*
5 | * Allow the camera to orbit around a target
6 | */
7 | const OrbitControls: typeof THREE.OrbitControls = orbitControlsCreator(THREE)
8 |
9 | /**
10 | * Creates orbit controller
11 | * @param domElement HTML canvas element
12 | */
13 | export const createOrbitControls = (camera: THREE.Camera, domElement?: HTMLElement) => {
14 | const orbit = new OrbitControls(camera, domElement)
15 |
16 | orbit.enableKeys = true
17 | orbit.enableZoom = true
18 |
19 | return orbit
20 | }
21 |
22 | /**
23 | * Creates lights for the scene
24 | * @param color Lights color
25 | */
26 | export const createLights = (color: number = 0xffffff): THREE.Light[] => {
27 | const ambientLight = new THREE.AmbientLight(color)
28 | const lights = []
29 |
30 | lights[0] = new THREE.PointLight(color, 1, 0)
31 | lights[1] = new THREE.PointLight(color, 1, 0)
32 | lights[2] = new THREE.PointLight(color, 1, 0)
33 |
34 | lights[0].position.set(0, 200, 0)
35 | lights[1].position.set(100, 200, 100)
36 | lights[2].position.set(-100, -200, -100)
37 |
38 | return [ambientLight, lights[0], lights[1], lights[2]]
39 | }
40 |
41 | /**
42 | * Creates webgl renderer
43 | * @param canvas The html canvas node
44 | */
45 | export const createRenderer = (canvas: HTMLCanvasElement): THREE.WebGLRenderer => {
46 | const renderer = new THREE.WebGLRenderer({
47 | canvas: canvas,
48 | antialias: true,
49 | alpha: true
50 | })
51 |
52 | // Clear color
53 | renderer.setClearColor(0x0)
54 |
55 | // Clear alpha
56 | renderer.setClearAlpha(0)
57 |
58 | // Pixel ration setting
59 | renderer.setPixelRatio(window.devicePixelRatio)
60 |
61 | // Size setting
62 | renderer.setSize(window.innerWidth, window.innerHeight)
63 |
64 | return renderer
65 | }
66 |
67 | /**
68 | * Creates camera object
69 | * @param initDistance Initial distance from center
70 | */
71 | export const createCamera = (initDistance: number = 80) => {
72 | const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000)
73 | camera.position.z = initDistance
74 |
75 | return camera
76 | }
77 |
78 | /**
79 | * Creates a clock object
80 | */
81 | export const createClock = () => new THREE.Clock()
82 |
83 | /**
84 | * Creates a scene object
85 | */
86 | export const createScene = () => new THREE.Scene()
87 |
--------------------------------------------------------------------------------
/lib/textureBuilder.ts:
--------------------------------------------------------------------------------
1 | import * as structs from '../const/structs'
2 | import { NF_MASKED, PALETTE_SIZE, PALETTE_ALPHA_INDEX, RGB_SIZE, RGBA_SIZE } from '../const/constants'
3 |
4 | /**
5 | * Build texture data from buffer
6 | * @param buffer The model buffer
7 | * @param texture Texture description
8 | * @returns Uint8ClampedArray with unpacked RGBA data of a texture
9 | */
10 | export const buildTexture = (buffer: ArrayBuffer, texture: structs.Texture): Uint8ClampedArray => {
11 | const textureArea: number = texture.width * texture.height
12 | const isTextureMasked: number = texture.flags & NF_MASKED
13 |
14 | const textureData = new Uint8Array(buffer, texture.index, textureArea)
15 |
16 | // Palette of colors
17 | const palette = new Uint8Array(buffer, texture.index + textureArea, PALETTE_SIZE)
18 |
19 | // RGB color which will be replaced with transparency
20 | const alphaColor: Uint8Array = palette.slice(PALETTE_ALPHA_INDEX, PALETTE_ALPHA_INDEX + RGB_SIZE)
21 |
22 | // Create new image buffer
23 | const imageBuffer = new Uint8ClampedArray(textureArea * RGBA_SIZE)
24 |
25 | // Parsing indexed color: every item in texture data is index of color in
26 | // colors palette
27 | for (let i = 0; i < textureData.length; i++) {
28 | const item = textureData[i]
29 |
30 | const paletteOffset = item * RGB_SIZE
31 | const pixelOffset = i * RGBA_SIZE
32 |
33 | // Checks is alpha color
34 | const isAlphaColor
35 | = palette[paletteOffset + 0] === alphaColor[0]
36 | && palette[paletteOffset + 1] === alphaColor[1]
37 | && palette[paletteOffset + 2] === alphaColor[2]
38 |
39 | if (isTextureMasked && isAlphaColor) {
40 | // This modifies the model's data. Sets the mask color to black.
41 | // This is also done by Jed's model viewer (export texture has black)
42 | imageBuffer[pixelOffset + 0] = 0 // red
43 | imageBuffer[pixelOffset + 1] = 0 // green
44 | imageBuffer[pixelOffset + 2] = 0 // blue
45 | imageBuffer[pixelOffset + 3] = 0 // alpha
46 | } else {
47 | // Just applying to texture image data
48 | imageBuffer[pixelOffset + 0] = palette[paletteOffset + 0] // red
49 | imageBuffer[pixelOffset + 1] = palette[paletteOffset + 1] // green
50 | imageBuffer[pixelOffset + 2] = palette[paletteOffset + 2] // blue
51 | imageBuffer[pixelOffset + 3] = 255 // alpha
52 | }
53 | }
54 |
55 | return imageBuffer
56 | }
57 |
--------------------------------------------------------------------------------
/modules.d.ts:
--------------------------------------------------------------------------------
1 | // Allowing import MDL-files
2 | declare module '*.mdl'
3 |
4 | declare module 'fast-png'
5 | declare module 'three-orbit-controls'
6 |
7 | declare module 'fast-dataview' {
8 | class FastDataView extends DataView {}
9 |
10 | // prettier-ignore
11 | namespace FastDataView {}
12 | export = FastDataView
13 | }
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "start": "webpack-dev-server --mode development --host 0.0.0.0",
4 | "build": "webpack --p",
5 | "test": "jest"
6 | },
7 | "author": "Danakt Frost ",
8 | "license": "MIT",
9 | "jest": {
10 | "moduleFileExtensions": [
11 | "ts",
12 | "tsx",
13 | "js"
14 | ],
15 | "transform": {
16 | "^.+\\.(ts|tsx)$": "ts-jest"
17 | },
18 | "globals": {
19 | "ts-jest": {
20 | "tsConfig": "tsconfig.json"
21 | }
22 | },
23 | "testMatch": [
24 | "**/__tests__/**/*.ts?(x)",
25 | "**/?(*.)+(spec|test).ts?(x)"
26 | ],
27 | "collectCoverage": true,
28 | "collectCoverageFrom": [
29 | "lib/**/*.ts"
30 | ]
31 | },
32 | "devDependencies": {
33 | "@babel/core": "^7.1.6",
34 | "@types/gl-matrix": "^2.4.0",
35 | "@types/html-webpack-plugin": "^2.30.3",
36 | "@types/jest": "^24.0.25",
37 | "@types/jest-image-snapshot": "^2.11.1",
38 | "@types/node": "^10.3.1",
39 | "@types/ramda": "^0.25.34",
40 | "@types/react": "^16.8.2",
41 | "@types/react-color": "^2.14.0",
42 | "@types/react-dom": "^16.8.0",
43 | "@types/react-dropzone": "^4.2.2",
44 | "@types/react-hot-loader": "^4.1.0",
45 | "@types/styled-components": "4.1.8",
46 | "@types/three": "^0.93.19",
47 | "awesome-typescript-loader": "^5.2.0",
48 | "babel-plugin-styled-components": "^1.8.0",
49 | "eslint": "^5.7.0",
50 | "eslint-config-prettier": "^2.9.0",
51 | "eslint-config-standard": "^11.0.0",
52 | "eslint-loader": "^1.9.0",
53 | "eslint-plugin-arca": "^0.6.0",
54 | "eslint-plugin-import": "^2.9.0",
55 | "eslint-plugin-node": "^6.0.1",
56 | "eslint-plugin-prettier": "^2.6.0",
57 | "eslint-plugin-promise": "^3.6.0",
58 | "eslint-plugin-react": "^7.11.1",
59 | "eslint-plugin-standard": "^3.0.1",
60 | "eslint-plugin-typescript": "^0.8.1",
61 | "fast-dataview": "^0.3.3",
62 | "file-loader": "^1.1.11",
63 | "html-webpack-plugin": "^3.2.0",
64 | "jest": "^24.9.0",
65 | "jest-image-snapshot": "^2.5.0",
66 | "prettier": "^1.11.1",
67 | "prettier-eslint": "^8.8.1",
68 | "ts-jest": "^24.2.0",
69 | "ts-node": "^8.5.4",
70 | "typescript": "^3.7.4",
71 | "typescript-eslint-parser": "^20.0.0",
72 | "webpack": "^4.0.0-beta.3",
73 | "webpack-cli": "^3.0.8",
74 | "webpack-dev-server": "^3.1.4"
75 | },
76 | "dependencies": {
77 | "fast-png": "^3.1.0",
78 | "gl-matrix": "^2.7.1",
79 | "libreact": "^2.8.1",
80 | "multi-array-view": "^0.1.0",
81 | "ramda": "^0.25.0",
82 | "react": "^16.8.1",
83 | "react-color": "^2.17.0",
84 | "react-dom": "^16.8.1",
85 | "react-dropzone": "^8.0.4",
86 | "react-hot-loader": "^4.6.5",
87 | "style-loader": "^0.23.1",
88 | "styled-components": "^4.4.1",
89 | "three": "^0.101.1",
90 | "three-orbit-controls": "^82.1.0"
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danakt/web-hlmv/2979a66eb3ccc5db8e59523c9c837cb2a5a502b9/screenshot.png
--------------------------------------------------------------------------------
/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Web Half-Life Model Viewer
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
45 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "lib": ["dom", "es7"],
6 | "strict": true,
7 | "experimentalDecorators": true,
8 | "emitDecoratorMetadata": true,
9 | "jsx": "react"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ui/App.tsx:
--------------------------------------------------------------------------------
1 | import * as leetModel from '../__mock__/leet.mdl'
2 | import * as React from 'react'
3 | import { hot } from 'react-hot-loader'
4 | import DropzoneContainer from 'react-dropzone'
5 | import { ModelController } from '../lib/modelController'
6 | import { ModelData } from '../lib/modelDataParser'
7 | import { LoadingScreen } from './LoadingScreen'
8 | import { Renderer } from './Renderer'
9 | import { Controller } from './Controller'
10 | import { GlobalStyles } from './GlobalStyles'
11 | import { Dropzone } from './Dropzone'
12 | import { BackgroundContainer } from './BackgroundContainer'
13 | import { FileContainer } from './FileContainer'
14 | import { GithubButton } from './GithubButton'
15 | import { StartScreen } from './StartScreen'
16 |
17 | /** Is need to show demo */
18 | export const App = hot(module)(() => {
19 | const [modelController, setModelController] = React.useState(undefined)
20 | const [modelData, setModelData] = React.useState(undefined)
21 | const [isDemoShowed] = React.useState(location.search.indexOf('?demo') === 0)
22 | const [demoFileUrl] = React.useState(leetModel)
23 |
24 | return (
25 |
26 | {({ buffer, isLoading }, { setFile, setFileUrl }) => (
27 | setFile(files[0])}>
28 | {({ getRootProps, getInputProps, isDragActive }) => (
29 |
30 |
31 | {({ backgroundColor }, { setBackgroundColor }) => (
32 |
33 |
34 |
35 |
36 |
37 | setFile(file)}
44 | />
45 |
46 | {(isDragActive || (!buffer && !isLoading)) && (
47 | setFile(file)}
51 | isDragActive={isDragActive}
52 | inputProps={getInputProps()}
53 | >
54 | {isDragActive ? (
55 | 'Drop model here...'
56 | ) : (
57 |
62 | )}
63 |
64 | )}
65 |
66 | {isLoading && Loading...}
67 |
68 | {buffer && !isLoading && (
69 |
70 |
75 |
76 | )}
77 |
78 | )}
79 |
80 |
81 | )}
82 |
83 | )}
84 |
85 | )
86 | })
87 |
--------------------------------------------------------------------------------
/ui/BackgroundContainer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { INITIAL_UI_BACKGROUND } from '../const/constants'
3 |
4 | const LOCAL_STORAGE_KEY = '__hlmv_background_color'
5 |
6 | type Data = {
7 | backgroundColor: string
8 | }
9 |
10 | type Actions = {
11 | setBackgroundColor: (color: string) => void
12 | }
13 |
14 | type Props = {
15 | children: (data: Data, actions: Actions) => React.ReactNode
16 | }
17 |
18 | /**
19 | * Background color container
20 | */
21 | export const BackgroundContainer = (props: Props) => {
22 | const [color, setColor] = React.useState(() => localStorage.getItem(LOCAL_STORAGE_KEY) || INITIAL_UI_BACKGROUND)
23 |
24 | React.useEffect(() => {
25 | localStorage.setItem(LOCAL_STORAGE_KEY, color)
26 | }, [color])
27 |
28 | return {props.children({ backgroundColor: color }, { setBackgroundColor: setColor })}
29 | }
30 |
--------------------------------------------------------------------------------
/ui/Controller.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import styled from 'styled-components'
3 | import { INITIAL_UI_BACKGROUND } from '../const/constants'
4 | import { ControllerContainer } from './ControllerContainer'
5 | import { ModelController } from '../lib/modelController'
6 | import { ModelData } from '../lib/modelDataParser'
7 | import { DatWrapper } from '../dat/DatWrapper'
8 | import { DatFolder } from '../dat/DatFolder'
9 | import { DatButton } from '../dat/DatButton'
10 | import { DatSelect } from '../dat/DatSelect'
11 | import { DatNumber } from '../dat/DatNumber'
12 | import { DatColor } from '../dat/DatColor'
13 | import { DatFile } from '../dat/DatFile'
14 | import { DatRange } from '../dat/DatRange'
15 |
16 | const StyledDatGui = styled(DatWrapper)`
17 | position: absolute;
18 | top: 15px;
19 | right: 15px;
20 | z-index: 3;
21 | `
22 |
23 | type Props = {
24 | backgroundColor: string
25 | modelController?: ModelController
26 | modelData?: ModelData
27 | isLoading: boolean
28 | onBackgroundColorUpdate: (color: string) => void
29 | onModelLoad: (file: File) => void
30 | }
31 |
32 | /**
33 | * Component with form of controlling model behavior
34 | */
35 | export const Controller = (props: Props) => {
36 | const { backgroundColor, modelController, modelData, isLoading } = props
37 |
38 | return (
39 |
40 | props.onModelLoad(file)}
43 | />
44 |
45 | props.onBackgroundColorUpdate(color)} />
46 | {backgroundColor !== INITIAL_UI_BACKGROUND && (
47 | props.onBackgroundColorUpdate(INITIAL_UI_BACKGROUND)}>
48 | Set default background color
49 |
50 | )}
51 |
52 | {!isLoading && modelController && modelData && (
53 |
54 | {(
55 | { isPaused, activeAnimationIndex: activeSequenceIndex, showedSubModels, frame, playbackRate },
56 | { togglePause, setAnimation, showSubModel, setPlaybackRate, setFrame, setTempPaused }
57 | ) => (
58 |
59 |
60 | item.label)}
64 | onChange={seqIndex => setAnimation(seqIndex)}
65 | />
66 | togglePause()}>{isPaused ? 'Play' : 'Pause'}
67 | setFrame(value)}
73 | onChangeStart={() => setTempPaused(true)}
74 | onChangeComplete={() => setTempPaused(false)}
75 | />
76 | setPlaybackRate(value)}
83 | />
84 |
85 |
86 |
95 |
96 |
97 |
98 | {modelData.bodyParts.map((bodyPart, bodyPartIndex) => (
99 | subModel.name)}
104 | onChange={subModelIndex => showSubModel(bodyPartIndex, subModelIndex)}
105 | />
106 | ))}
107 |
108 |
109 | )}
110 |
111 | )}
112 |
113 | )
114 | }
115 |
--------------------------------------------------------------------------------
/ui/ControllerContainer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { ModelController, ModelState } from '../lib/modelController'
3 | import { ModelData } from '../lib/modelDataParser'
4 |
5 | type Data = ModelState
6 |
7 | type Actions = {
8 | togglePause: () => void
9 | setAnimation: (seqIndex: number) => void
10 | showSubModel: (bodyPartIndex: number, subModelIndex: number) => void
11 | setPlaybackRate: (rate: number) => void
12 | setFrame: (frame: number) => void
13 | setTempPaused: (isTempPaused: boolean) => void
14 | }
15 |
16 | type Props = {
17 | modelData: ModelData
18 | modelController: ModelController
19 | children: (data: Data, actions: Actions) => React.ReactNode
20 | }
21 |
22 | /**
23 | * Controller container
24 | */
25 | export const ControllerContainer = (props: Props) => {
26 | const [modelState, setModelState] = React.useState(() => props.modelController.getCurrentState())
27 | const [isPrevPaused, setPrevPaused] = React.useState(false)
28 |
29 | React.useEffect(() => {
30 | if (props.modelController) {
31 | setModelState(props.modelController.getCurrentState())
32 | }
33 | }, [props.modelController])
34 |
35 | // Updating animation frame index
36 | React.useEffect(() => {
37 | const frameDelay = (1 / props.modelData.sequences[modelState.activeAnimationIndex].fps) * 1000
38 | const intervalId = setInterval(() => setModelState(props.modelController.getCurrentState()), frameDelay)
39 |
40 | return () => clearInterval(intervalId)
41 | }, [props.modelData, modelState.activeAnimationIndex])
42 |
43 | return (
44 |
45 | {props.children(modelState, {
46 | togglePause: () => setModelState(props.modelController.setPause(!modelState.isPaused)),
47 | setAnimation: seqIndex => setModelState(props.modelController.setAnimation(seqIndex)),
48 | showSubModel: (bodyPartIndex, subModelIndex) =>
49 | setModelState(props.modelController.showSubModel(bodyPartIndex, subModelIndex)),
50 | setPlaybackRate: rate => setModelState(props.modelController.setPlaybackRate(rate)),
51 | setFrame: frame => setModelState(props.modelController.setFrame(frame)),
52 | setTempPaused: isTempPaused => {
53 | if (isTempPaused) {
54 | setPrevPaused(modelState.isPaused)
55 | setModelState(props.modelController.setPause(isTempPaused))
56 | } else {
57 | setModelState(props.modelController.setPause(isPrevPaused))
58 | }
59 | }
60 | })}
61 |
62 | )
63 | }
64 |
--------------------------------------------------------------------------------
/ui/Dropzone.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import styled from 'styled-components'
3 | import { DropzoneInputProps } from 'react-dropzone'
4 |
5 | const Wrapper = styled.div<{ color: string; isDragActive: boolean }>`
6 | position: relative;
7 | width: 100vw;
8 | height: 100vh;
9 | background-color: ${props => props.color};
10 | z-index: ${props => (props.isDragActive ? 5 : 'auto')};
11 | `
12 |
13 | const Input = styled.input`
14 | display: block;
15 | position: absolute;
16 | top: 0;
17 | left: 0;
18 | width: 100%;
19 | height: 100%;
20 | z-index: 2;
21 | `
22 |
23 | const BorderedBox = styled.div`
24 | position: absolute;
25 | left: 25px;
26 | top: 25px;
27 | width: calc(100% - 50px);
28 | height: calc(100% - 50px);
29 | z-index: 1;
30 | `
31 |
32 | const Description = styled.div`
33 | position: absolute;
34 | left: 50%;
35 | top: 50%;
36 | transform: translate(-50%, -50%);
37 | `
38 |
39 | type Props = {
40 | isDragActive: boolean
41 | backgroundColor: string
42 | inputProps: DropzoneInputProps
43 | onClick?: (event: React.MouseEvent) => void
44 | onFileLoad: (file: File) => void
45 | children: React.ReactNode
46 | }
47 |
48 | /**
49 | * Dropzone
50 | */
51 | export const Dropzone = (props: Props) => {
52 | const transparentColor = React.useMemo(() => {
53 | const r = parseInt(props.backgroundColor.substring(1, 3), 16)
54 | const g = parseInt(props.backgroundColor.substring(3, 5), 16)
55 | const b = parseInt(props.backgroundColor.substring(5, 7), 16)
56 |
57 | // Setting half-opacity
58 | return `rgba(${r}, ${g}, ${b}, ${0.7})`
59 | }, [props.backgroundColor])
60 |
61 | return (
62 |
63 | {!props.isDragActive && }
64 |
65 |
66 | {props.children}
67 |
68 |
69 | )
70 | }
71 |
--------------------------------------------------------------------------------
/ui/FileContainer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | type Data = {
4 | buffer: null | ArrayBuffer
5 | isLoading: boolean
6 | }
7 |
8 | type Actions = {
9 | setFile: (file: File) => void
10 | setFileUrl: (fileUrl: string) => void
11 | }
12 |
13 | type Props = {
14 | defaultFileUrl?: string
15 | children: (data: Data, actions: Actions) => React.ReactNode
16 | }
17 |
18 | /**
19 | * Manages the model buffer
20 | */
21 | export const FileContainer = (props: Props) => {
22 | const [fileUrl, setFileUrl] = React.useState(props.defaultFileUrl)
23 | const [isLoading, setLoadingState] = React.useState(typeof fileUrl === 'string')
24 | const [buffer, setBuffer] = React.useState(null)
25 |
26 | /** Loads default model file and saves it to state */
27 | const loadingDemo = async (modelFile: string) => {
28 | setLoadingState(true)
29 | const resp = await fetch(modelFile)
30 | const buffer = await resp.arrayBuffer()
31 | setBuffer(buffer)
32 | setLoadingState(false)
33 | }
34 |
35 | /** Loads file to buffer */
36 | const loadFile = (file: File) => {
37 | const fileReader = new FileReader()
38 |
39 | fileReader.addEventListener('load', () => {
40 | setBuffer(fileReader.result as ArrayBuffer)
41 | setLoadingState(false)
42 | })
43 |
44 | fileReader.readAsArrayBuffer(file)
45 | setLoadingState(true)
46 | }
47 |
48 | React.useEffect(() => {
49 | if (typeof fileUrl === 'string') {
50 | loadingDemo(fileUrl)
51 | }
52 | }, [fileUrl])
53 |
54 | return (
55 |
56 | {props.children(
57 | { buffer, isLoading },
58 | {
59 | setFile: loadFile,
60 | setFileUrl
61 | }
62 | )}
63 |
64 | )
65 | }
66 |
--------------------------------------------------------------------------------
/ui/GithubButton.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import styled from 'styled-components'
3 |
4 | const Wrapper = styled.div`
5 | position: absolute;
6 | left: 15px;
7 | top: 15px;
8 | z-index: 4;
9 | `
10 |
11 | /**
12 | * Github button to get started :)
13 | */
14 | export const GithubButton = () => (
15 |
16 |
22 | Star
23 |
24 |
25 | )
26 |
--------------------------------------------------------------------------------
/ui/GlobalStyles.tsx:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components'
2 |
3 | /** Reset.css */
4 | export const resetCss = `
5 | html, body, div, span, applet, object, iframe,
6 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
7 | a, abbr, acronym, address, big, cite, code,
8 | del, dfn, em, img, ins, kbd, q, s, samp,
9 | small, strike, strong, sub, sup, tt, var,
10 | b, u, i, center,
11 | dl, dt, dd, ol, ul, li,
12 | fieldset, form, label, legend,
13 | table, caption, tbody, tfoot, thead, tr, th, td,
14 | article, aside, canvas, details, embed,
15 | figure, figcaption, footer, header, hgroup,
16 | menu, nav, output, ruby, section, summary,
17 | time, mark, audio, video {
18 | margin: 0;
19 | padding: 0;
20 | border: 0;
21 | font-size: 100%;
22 | font: inherit;
23 | vertical-align: baseline;
24 | }
25 | article, aside, details, figcaption, figure,
26 | footer, header, hgroup, menu, nav, section {
27 | display: block;
28 | }
29 | body {
30 | line-height: 1;
31 | }
32 | ol, ul {
33 | list-style: none;
34 | }
35 | blockquote, q {
36 | quotes: none;
37 | }
38 | blockquote:before, blockquote:after,
39 | q:before, q:after {
40 | content: '';
41 | content: none;
42 | }
43 | table {
44 | border-collapse: collapse;
45 | border-spacing: 0;
46 | }
47 | `
48 |
49 | type Props = {
50 | backgroundColor: string
51 | color: string
52 | }
53 |
54 | /** Component with global styles of the app */
55 | export const GlobalStyles = createGlobalStyle`
56 | ${resetCss}
57 |
58 | @import url('https://fonts.googleapis.com/css?family=Inconsolata');
59 |
60 | *,
61 | *::before,
62 | *::after {
63 | box-sizing: border-box;
64 | }
65 |
66 | html, body {
67 | height: 100%;
68 | }
69 |
70 | body {
71 | padding: 0;
72 | margin: 0;
73 | background: ${props => props.backgroundColor};
74 | font-family: 'Inconsolata', monospace;
75 | font-size: 14px;
76 | color: ${props => props.color};
77 | transition: background-color .3s ease-out;
78 | overflow: hidden;
79 | }
80 |
81 | input {
82 | font-family: 'Inconsolata', monospace;
83 | font-size: 14px;
84 | }
85 |
86 | a {
87 | color: #fff
88 | }
89 | `
90 |
--------------------------------------------------------------------------------
/ui/LoadingScreen.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import styled from 'styled-components'
3 |
4 | const Wrapper = styled.div`
5 | position: absolute;
6 | left: 50%;
7 | top: 50%;
8 | transform: translate(-50%, -50%);
9 | `
10 |
11 | const Text = styled.div`
12 | position: absolute;
13 | left: 50%;
14 | top: 50%;
15 | transform: translate(-50%, -50%);
16 | `
17 |
18 | type Props = {
19 | children: React.ReactNode
20 | }
21 |
22 | export const LoadingScreen = (props: Props) => (
23 |
24 | {props.children}
25 |
26 | )
27 |
--------------------------------------------------------------------------------
/ui/Renderer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { WindowSizeSensor } from 'libreact/lib/WindowSizeSensor'
3 | import { ModelData, parseModel } from '../lib/modelDataParser'
4 | import { buildTexture } from '../lib/textureBuilder'
5 | import { prepareRenderData, createModelMeshes, createContainer } from '../lib/modelRenderer'
6 | import { createModelController, ModelController } from '../lib/modelController'
7 | import {
8 | createOrbitControls,
9 | createRenderer,
10 | createCamera,
11 | createLights,
12 | createClock,
13 | createScene
14 | } from '../lib/screneRenderer'
15 |
16 | const useAnimationFrame = function void>(callback: T) {
17 | const callbackRef = React.useRef(callback)
18 | React.useEffect(() => (callbackRef.current = callback), [callback])
19 |
20 | const loop = () => {
21 | ;(frameRef as any).current = requestAnimationFrame(loop)
22 |
23 | callbackRef.current()
24 | }
25 |
26 | const frameRef = React.useRef(null)
27 |
28 | React.useLayoutEffect(() => {
29 | ;(frameRef as any).current = requestAnimationFrame(loop)
30 |
31 | return () => cancelAnimationFrame(frameRef.current!)
32 | }, [])
33 | }
34 |
35 | type Props = {
36 | modelBuffer: ArrayBuffer
37 | setModelController: (controller: ModelController) => void
38 | setModelData: (modelData: ModelData) => void
39 | }
40 |
41 | export const Renderer = (props: Props) => {
42 | // Canvas reference
43 | const canvasRef = React.useRef(null)
44 |
45 | // Camera
46 | const camera = React.useMemo(() => createCamera(), [])
47 | // Clock
48 | const clock = React.useMemo(() => createClock(), [])
49 | // Clock
50 | const scene = React.useMemo(() => createScene(), [])
51 |
52 | // Three renderer
53 | const renderer: THREE.WebGLRenderer | null = React.useMemo(
54 | () => canvasRef.current && createRenderer(canvasRef.current),
55 | [canvasRef.current]
56 | )
57 |
58 | // Orbit controller
59 | const orbitControls: THREE.OrbitControls | null = React.useMemo(
60 | () => canvasRef.current && createOrbitControls(camera, canvasRef.current),
61 | [camera, canvasRef.current]
62 | )
63 |
64 | // Scene lights
65 | // Note: you can pass lights color to arguments
66 | const lights = React.useMemo(() => createLights(), [])
67 |
68 | // Parsing the model buffer
69 | const modelData: ModelData = React.useMemo(() => parseModel(props.modelBuffer), [props.modelBuffer])
70 |
71 | // Meshes render
72 | const meshesRenderData = React.useMemo(() => prepareRenderData(modelData), [modelData])
73 |
74 | // Textures preparing
75 | const textures = React.useMemo(() => modelData.textures.map(texture => buildTexture(props.modelBuffer, texture)), [
76 | modelData
77 | ])
78 |
79 | // Generation meshes
80 | const meshes = React.useMemo(() => createModelMeshes(meshesRenderData, modelData, textures), [
81 | meshesRenderData,
82 | modelData,
83 | textures
84 | ])
85 |
86 | // Creating model controller
87 | const controller: ModelController = React.useMemo(() => createModelController(meshes, meshesRenderData, modelData), [
88 | meshes,
89 | meshesRenderData,
90 | modelData
91 | ])
92 |
93 | // Mesh container
94 | const container = React.useMemo(() => createContainer(meshes), [meshes])
95 |
96 | // Updating scene objects
97 | React.useEffect(() => {
98 | if (scene) {
99 | scene.add(container)
100 | scene.add(...lights)
101 |
102 | return () => {
103 | scene.remove(container)
104 | scene.remove(...lights)
105 | }
106 | }
107 | }, [container, scene, lights])
108 |
109 | // Update model data
110 | React.useEffect(() => props.setModelData(modelData), [modelData])
111 |
112 | // Update controller
113 | React.useEffect(() => props.setModelController(controller), [controller])
114 |
115 | // Updating animation frame
116 | useAnimationFrame(() => {
117 | if (orbitControls) {
118 | orbitControls.update()
119 | }
120 |
121 | const delta = clock.getDelta()
122 | controller.update(delta)
123 |
124 | if (renderer) {
125 | renderer.render(scene, camera)
126 | }
127 | })
128 |
129 | return (
130 | {
132 | camera.aspect = window.innerWidth / window.innerHeight
133 | camera.updateProjectionMatrix()
134 |
135 | if (renderer) {
136 | renderer.setSize(size.width, size.height)
137 | }
138 | }}
139 | >
140 | {(state: any) => (
141 |
148 | )}
149 |
150 | )
151 | }
152 |
--------------------------------------------------------------------------------
/ui/StartScreen.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | type Props = {
4 | demoFileUrl: string
5 | selectFile?: (event: React.MouseEvent) => void
6 | setFileUrl: (url: string) => void
7 | }
8 |
9 | export const StartScreen = (props: Props) => (
10 |
11 | Try to drop some model here, or{' '}
12 | {
15 | if (props.selectFile) {
16 | props.selectFile(event)
17 | }
18 |
19 | event.preventDefault()
20 | }}
21 | >
22 | click here
23 | {' '}
24 | to select a model to upload.
25 |
Also, you can{' '}
26 | {
29 | props.setFileUrl(props.demoFileUrl)
30 | event.preventDefault()
31 | }}
32 | >
33 | start demo file viewing
34 |
35 | .
36 |
37 | )
38 |
--------------------------------------------------------------------------------
/webpack.config.ts:
--------------------------------------------------------------------------------
1 | import { Configuration } from 'webpack'
2 | import * as path from 'path'
3 | import * as HtmlWebpackPlugin from 'html-webpack-plugin'
4 |
5 | const config: Configuration = {
6 | devtool: 'source-map',
7 | entry: './index.tsx',
8 | module: {
9 | rules: [
10 | {
11 | test: /\.tsx?$/,
12 | use: {
13 | loader: 'awesome-typescript-loader',
14 | options: {
15 | useBabel: true,
16 | babelOptions: {
17 | babelrc: false,
18 | plugins: ['react-hot-loader/babel', 'babel-plugin-styled-components']
19 | },
20 | babelCore: '@babel/core'
21 | }
22 | },
23 | exclude: /node_modules/
24 | },
25 |
26 | // Model loading
27 | {
28 | test: /\.mdl$/,
29 | use: 'file-loader'
30 | }
31 | ]
32 | },
33 | resolve: {
34 | extensions: ['.tsx', '.ts', '.js']
35 | },
36 | output: {
37 | filename: 'bundle.[contenthash].js',
38 | path: path.resolve(__dirname, 'dist')
39 | },
40 | plugins: [
41 | new HtmlWebpackPlugin({
42 | template: path.resolve(__dirname, 'template.html')
43 | })
44 | ]
45 | }
46 |
47 | export default config
48 |
--------------------------------------------------------------------------------