| - [Packages](#threepipe-packages) | - [Packages](#threepipe-packages) | ||||
| - [@threepipe/plugin-tweakpane](#threepipeplugin-tweakpane) Tweakpane UI Plugin | - [@threepipe/plugin-tweakpane](#threepipeplugin-tweakpane) Tweakpane UI Plugin | ||||
| - [@threepipe/plugin-tweakpane-editor](#threepipeplugin-tweakpane-editor) - Tweakpane Editor Plugin | - [@threepipe/plugin-tweakpane-editor](#threepipeplugin-tweakpane-editor) - Tweakpane Editor Plugin | ||||
| - [@threepipe/plugin-extra-importers](#threepipeplugin-extra-importers) - Plugin for loading even more file types. | |||||
| - [@threepipe/plugin-extra-importers](#threepipeplugin-extra-importers) - Plugin for loading more file types supported by loaders in three.js | |||||
| - [@threepipe/plugin-blend-importer](#threepipeplugin-blend-importer) - Blender to add support for loading .blend file | |||||
| ## Getting Started | ## Getting Started | ||||
| ``` | ``` | ||||
| Remove the `<IObject3D>` if using javascript and not typescript. | Remove the `<IObject3D>` if using javascript and not typescript. | ||||
| ## @threepipe/plugin-blend-importer | |||||
| Exports [BlendImporterPlugin](https://threepipe.org/plugins/blend-importer/docs/classes/BlendLoadPlugin.html) which adds support for loading .blend files. | |||||
| It uses [js.blend](https://github.com/acweathersby/js.blend) for parsing blend file structure. | |||||
| Note: This is still a WIP. | |||||
| Currently working: `Mesh`, `BufferGeometry` and basic `PointLight`. | |||||
| To be added: `PhysicalMaterial`, `UnlitMaterial` (similar to blender-gltf-io plugin) | |||||
| Example: https://threepipe.org/examples/#blend-load/ | |||||
| Source Code: [plugins/blend-importer/src/index.ts](plugins/blend-importer/src/index.ts) | |||||
| API Reference: [@threepipe/plugin-blend-importer](https://threepipe.org/plugins/blend-importer/docs) | |||||
| NPM: `npm install @threepipe/plugin-blend-importer` | |||||
| ```typescript | |||||
| import {ThreeViewer} from 'threepipe' | |||||
| import {BlendLoadPlugin} from '@threepipe/plugin-blend-importer' | |||||
| const viewer = new ThreeViewer({...}) | |||||
| viewer.addPluginSync(BlendLoadPlugin) | |||||
| // Now load any .blend file. | |||||
| const model = await viewer.load<IObject3D>('path/to/file.blend') | |||||
| // To load the file as a data url, use the correct mimetype | |||||
| const model1 = await viewer.load<IObject3D>('data:application/x-blender;base64,...') | |||||
| ``` | |||||
| [//]: # ( TODO: The plugin should parse and references to other assets and find them relative to the .blend file or the current location.) | |||||
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>Blend Load</title> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
| <!-- Import maps polyfill --> | |||||
| <!-- Remove this when import maps will be widely supported --> | |||||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||||
| <script type="importmap"> | |||||
| { | |||||
| "imports": { | |||||
| "threepipe": "./../../dist/index.mjs", | |||||
| "@threepipe/plugin-blend-importer": "./../../plugins/blend-importer/dist/index.mjs" | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style id="example-style"> | |||||
| html, body, #canvas-container, #mcanvas { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| margin: 0; | |||||
| overflow: hidden; | |||||
| } | |||||
| </style> | |||||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||||
| <script id="example-script" type="module"> | |||||
| import {_testFinish, ThreeViewer} from 'threepipe' | |||||
| import {BlendLoadPlugin} from '@threepipe/plugin-blend-importer' | |||||
| const viewer = new ThreeViewer({canvas: document.getElementById('mcanvas')}) | |||||
| viewer.addPluginsSync([BlendLoadPlugin]) | |||||
| async function init() { | |||||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||||
| const result = await viewer.load('https://asset-samples.threepipe.org/minimal/default-cube.blend', { | |||||
| autoCenter: true, | |||||
| autoScale: false, | |||||
| }) | |||||
| console.log(result) | |||||
| } | |||||
| init().then(_testFinish) | |||||
| </script> | |||||
| </head> | |||||
| <body> | |||||
| <div id="canvas-container"> | |||||
| <canvas id="mcanvas"></canvas> | |||||
| </div> | |||||
| </body> |
| <li><a href="./stl-load/">STL Load </a></li> | <li><a href="./stl-load/">STL Load </a></li> | ||||
| <li><a href="./ktx2-load/">KTX2 Load </a></li> | <li><a href="./ktx2-load/">KTX2 Load </a></li> | ||||
| <li><a href="./ktx-load/">KTX Load </a></li> | <li><a href="./ktx-load/">KTX Load </a></li> | ||||
| <li><a href="./blend-load/">BLEND Load </a></li> | |||||
| <li><a href="./extra-importer-plugins/">Extra(3ds, 3mf, collada, amf, bvh, vox, gcode, mdd, pcd, tilt, wrl, ldraw, vtk, xyz) Load </a></li> | <li><a href="./extra-importer-plugins/">Extra(3ds, 3mf, collada, amf, bvh, vox, gcode, mdd, pcd, tilt, wrl, ldraw, vtk, xyz) Load </a></li> | ||||
| </ul> | </ul> | ||||
| <h2 class="category">Export</h2> | <h2 class="category">Export</h2> |
| "threepipe": "./../../dist/index.mjs", | "threepipe": "./../../dist/index.mjs", | ||||
| "@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/dist/index.mjs", | "@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/dist/index.mjs", | ||||
| "@threepipe/plugin-tweakpane-editor": "./../../plugins/tweakpane-editor/dist/index.mjs", | "@threepipe/plugin-tweakpane-editor": "./../../plugins/tweakpane-editor/dist/index.mjs", | ||||
| "@threepipe/plugin-extra-importers": "./../../plugins/extra-importers/dist/index.mjs" | |||||
| "@threepipe/plugin-extra-importers": "./../../plugins/extra-importers/dist/index.mjs", | |||||
| "@threepipe/plugin-blend-importer": "./../../plugins/blend-importer/dist/index.mjs" | |||||
| } | } | ||||
| } | } | ||||
| KTX2LoadPlugin, | KTX2LoadPlugin, | ||||
| KTXLoadPlugin, | KTXLoadPlugin, | ||||
| NormalBufferPlugin, | NormalBufferPlugin, | ||||
| PickingPlugin, | |||||
| PLYLoadPlugin, | PLYLoadPlugin, | ||||
| ProgressivePlugin, | ProgressivePlugin, | ||||
| RenderTargetPreviewPlugin, | RenderTargetPreviewPlugin, | ||||
| } from 'threepipe' | } from 'threepipe' | ||||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | ||||
| import {TweakpaneEditorPlugin} from '@threepipe/plugin-tweakpane-editor' | import {TweakpaneEditorPlugin} from '@threepipe/plugin-tweakpane-editor' | ||||
| import {BlendLoadPlugin} from '@threepipe/plugin-blend-importer' | |||||
| import {extraImportPlugins} from '@threepipe/plugin-extra-importers' | import {extraImportPlugins} from '@threepipe/plugin-extra-importers' | ||||
| async function init() { | async function init() { | ||||
| await viewer.addPlugins([ | await viewer.addPlugins([ | ||||
| new ProgressivePlugin(), | new ProgressivePlugin(), | ||||
| new GLTFAnimationPlugin(), | |||||
| new CameraViewPlugin(), | |||||
| new ViewerUiConfigPlugin(), | |||||
| GLTFAnimationPlugin, | |||||
| PickingPlugin, | |||||
| CameraViewPlugin, | |||||
| ViewerUiConfigPlugin, | |||||
| // new SceneUiConfigPlugin(), // this is already in ViewerUiPlugin | // new SceneUiConfigPlugin(), // this is already in ViewerUiPlugin | ||||
| new DepthBufferPlugin(HalfFloatType, true, true), | new DepthBufferPlugin(HalfFloatType, true, true), | ||||
| new NormalBufferPlugin(HalfFloatType, false), | new NormalBufferPlugin(HalfFloatType, false), | ||||
| Rhino3dmLoadPlugin, | Rhino3dmLoadPlugin, | ||||
| STLLoadPlugin, | STLLoadPlugin, | ||||
| USDZLoadPlugin, | USDZLoadPlugin, | ||||
| BlendLoadPlugin, | |||||
| ...extraImportPlugins, | ...extraImportPlugins, | ||||
| ]) | ]) | ||||
| editor.loadPlugins({ | editor.loadPlugins({ | ||||
| ['Viewer']: [ViewerUiConfigPlugin, SceneUiConfigPlugin, DropzonePlugin, FullScreenPlugin], | ['Viewer']: [ViewerUiConfigPlugin, SceneUiConfigPlugin, DropzonePlugin, FullScreenPlugin], | ||||
| ['Interaction']: [PickingPlugin], | |||||
| ['GBuffer']: [DepthBufferPlugin, NormalBufferPlugin], | ['GBuffer']: [DepthBufferPlugin, NormalBufferPlugin], | ||||
| ['Post-processing']: [TonemapPlugin, ProgressivePlugin, FrameFadePlugin], | ['Post-processing']: [TonemapPlugin, ProgressivePlugin, FrameFadePlugin], | ||||
| ['Animation']: [GLTFAnimationPlugin, CameraViewPlugin], | ['Animation']: [GLTFAnimationPlugin, CameraViewPlugin], |
| { | { | ||||
| "name": "threepipe", | "name": "threepipe", | ||||
| "version": "0.0.13", | |||||
| "version": "0.0.14", | |||||
| "lockfileVersion": 2, | "lockfileVersion": 2, | ||||
| "requires": true, | "requires": true, | ||||
| "packages": { | "packages": { | ||||
| "": { | "": { | ||||
| "name": "threepipe", | "name": "threepipe", | ||||
| "version": "0.0.13", | |||||
| "version": "0.0.14", | |||||
| "license": "Apache-2.0", | "license": "Apache-2.0", | ||||
| "dependencies": { | "dependencies": { | ||||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1016/package.tgz", | "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1016/package.tgz", |
| { | { | ||||
| "name": "threepipe", | "name": "threepipe", | ||||
| "version": "0.0.13", | |||||
| "version": "0.0.14", | |||||
| "description": "A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.", | "description": "A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.", | ||||
| "main": "src/index.ts", | "main": "src/index.ts", | ||||
| "module": "dist/index.mjs", | "module": "dist/index.mjs", |
| { | |||||
| "name": "@threepipe/plugin-blend-importer", | |||||
| "version": "0.0.1", | |||||
| "lockfileVersion": 2, | |||||
| "requires": true, | |||||
| "packages": { | |||||
| "": { | |||||
| "name": "@threepipe/plugin-blend-importer", | |||||
| "version": "0.0.1", | |||||
| "license": "Apache-2.0", | |||||
| "dependencies": { | |||||
| "threepipe": "file:./../../src/" | |||||
| }, | |||||
| "devDependencies": {} | |||||
| }, | |||||
| "../../src": {}, | |||||
| "../tweakpane/src": { | |||||
| "extraneous": true | |||||
| }, | |||||
| "node_modules/threepipe": { | |||||
| "resolved": "../../src", | |||||
| "link": true | |||||
| } | |||||
| }, | |||||
| "dependencies": { | |||||
| "threepipe": { | |||||
| "version": "file:../../src" | |||||
| } | |||||
| } | |||||
| } |
| { | |||||
| "name": "@threepipe/plugin-blend-importer", | |||||
| "description": "Basic importer for .blend file", | |||||
| "version": "0.0.1", | |||||
| "devDependencies": { | |||||
| }, | |||||
| "dependencies": { | |||||
| "threepipe": "file:./../../src/" | |||||
| }, | |||||
| "clean-package": { | |||||
| "remove": [ | |||||
| "clean-package", | |||||
| "scripts", | |||||
| "devDependencies", | |||||
| "//", | |||||
| "markdown-to-html" | |||||
| ], | |||||
| "replace": { | |||||
| "dependencies": { | |||||
| "threepipe": "^0.0.13" | |||||
| } | |||||
| } | |||||
| }, | |||||
| "type": "module", | |||||
| "main": "dist/index.js", | |||||
| "module": "dist/index.mjs", | |||||
| "types": "dist/index.d.ts", | |||||
| "files": [ | |||||
| "dist", | |||||
| "src" | |||||
| ], | |||||
| "scripts": { | |||||
| "new:pack": "npm run prepare && clean-package && npm pack && clean-package restore", | |||||
| "new:publish": "npm run prepare && clean-package && npm publish --access public && clean-package restore", | |||||
| "prepare": "npm run build", | |||||
| "build": "rimraf dist && NODE_ENV=production rollup -c", | |||||
| "dev": "rollup -c -w", | |||||
| "docs": "rimraf docs && npx typedoc" | |||||
| }, | |||||
| "author": "repalash <palash@shaders.app>", | |||||
| "license": "Apache-2.0", | |||||
| "keywords": [ | |||||
| "three", | |||||
| "three.js", | |||||
| "threepipe", | |||||
| "editor", | |||||
| "plugin" | |||||
| ], | |||||
| "bugs": { | |||||
| "url": "https://github.com/repalash/threepipe/issues" | |||||
| }, | |||||
| "homepage": "https://github.com/repalash/threepipe#readme", | |||||
| "repository": { | |||||
| "type": "git", | |||||
| "url": "git://github.com/repalash/threepipe.git" | |||||
| } | |||||
| } |
| // rollup.config.js | |||||
| import commonjs from '@rollup/plugin-commonjs'; | |||||
| import json from '@rollup/plugin-json'; | |||||
| import resolve from '@rollup/plugin-node-resolve'; | |||||
| import typescript from '@rollup/plugin-typescript'; | |||||
| import license from 'rollup-plugin-license' | |||||
| import packageJson from './package.json' assert {type: 'json'}; | |||||
| import path from 'path' | |||||
| import {fileURLToPath} from 'url'; | |||||
| import postcss from 'rollup-plugin-postcss' | |||||
| import replace from '@rollup/plugin-replace' | |||||
| import terser from "@rollup/plugin-terser"; | |||||
| const __filename = fileURLToPath(import.meta.url); | |||||
| const __dirname = path.dirname(__filename); | |||||
| const {name, version, author} = packageJson | |||||
| // const {main, module, browser} = packageJson["clean-package"].replace | |||||
| const isProduction = process.env.NODE_ENV === 'production' | |||||
| const settings = { | |||||
| globals: { | |||||
| "three": "threepipe", | |||||
| "threepipe": "threepipe" | |||||
| }, | |||||
| sourcemap: true | |||||
| } | |||||
| export default { | |||||
| input: './src/index.ts', | |||||
| output: [ | |||||
| // { | |||||
| // file: main, | |||||
| // name: main, | |||||
| // ...settings, | |||||
| // format: 'cjs', | |||||
| // plugins: [ | |||||
| // isProduction && terser() | |||||
| // ] | |||||
| // }, | |||||
| { | |||||
| file: './dist/index.mjs', | |||||
| ...settings, | |||||
| name: name, | |||||
| format: 'es', | |||||
| plugins: [ | |||||
| isProduction && terser() | |||||
| ] | |||||
| }, | |||||
| { | |||||
| file: './dist/index.js', | |||||
| ...settings, | |||||
| name: name, | |||||
| format: 'umd', | |||||
| plugins: [ | |||||
| isProduction && terser() | |||||
| ] | |||||
| } | |||||
| ], | |||||
| external: Object.keys(settings.globals), | |||||
| plugins: [ | |||||
| replace({ | |||||
| 'from \'three\'': 'from \'threepipe\'', | |||||
| delimiters: ['', ''], | |||||
| }), | |||||
| replace({ | |||||
| 'process.env.NODE_ENV': JSON.stringify('production'), | |||||
| }), | |||||
| postcss({ | |||||
| modules: false, | |||||
| autoModules: true, // todo; issues with typescript import css, because inject is false | |||||
| inject: false, | |||||
| minimize: isProduction, | |||||
| // Or with custom options for `postcss-modules` | |||||
| }), | |||||
| json(), | |||||
| resolve({}), | |||||
| typescript({ | |||||
| }), | |||||
| commonjs({ | |||||
| include: 'node_modules/**', | |||||
| extensions: ['.js'], | |||||
| ignoreGlobal: false, | |||||
| sourceMap: false | |||||
| }), | |||||
| license({ | |||||
| banner: ` | |||||
| @license | |||||
| ${name} v${version} | |||||
| Copyright 2022<%= moment().format('YYYY') > 2022 ? '-' + moment().format('YYYY') : null %> ${author} | |||||
| ${packageJson.license} License | |||||
| `, | |||||
| thirdParty: { | |||||
| output: path.join(__dirname, 'dist', 'dependencies.txt'), | |||||
| includePrivate: true, // Default is false. | |||||
| }, | |||||
| }) | |||||
| ] | |||||
| } |
| import {AnyOptions, BaseImporterPlugin, FileLoader, ILoader, Importer, Object3D, Scene} from 'threepipe' | |||||
| import {parseBlend} from './js-blend/main.js' | |||||
| import {createObjects} from './loader' | |||||
| /** | |||||
| * Adds support for loading Blend `.blend`, `application/x-blender` files and data uris | |||||
| */ | |||||
| export class BlendLoadPlugin extends BaseImporterPlugin { | |||||
| public static readonly PluginType = 'BlendLoadPlugin' | |||||
| constructor() { | |||||
| super() | |||||
| } | |||||
| protected _importer = new Importer(class extends FileLoader implements ILoader { | |||||
| async loadAsync(url: string, onProgress?: (event: ProgressEvent) => void): Promise<any> { | |||||
| this.setResponseType('arraybuffer') | |||||
| const res = (await super.loadAsync(url, onProgress)) as ArrayBuffer | |||||
| const blend = await parseBlend(res) | |||||
| const objects = await createObjects(blend) | |||||
| const root = new Object3D() | |||||
| root.add(...objects) | |||||
| // console.log(res, blend, root) | |||||
| blend.scene = root | |||||
| return blend | |||||
| } | |||||
| transform(res: any, _: AnyOptions): Scene { | |||||
| // console.log(res) | |||||
| // res.scene.userData.kinematics = res.kinematics | |||||
| // res.scene.userData.library = res.library | |||||
| return res.scene | |||||
| } | |||||
| }, ['blend'], ['application/x-blender'], true) | |||||
| } |
| declare module '*.txt' { | |||||
| const content: string | |||||
| export default content | |||||
| } | |||||
| declare module '*.glsl' { | |||||
| const content: string | |||||
| export default content | |||||
| } | |||||
| declare module '*.vert' { | |||||
| const content: string | |||||
| export default content | |||||
| } | |||||
| declare module '*.frag' { | |||||
| const content: string | |||||
| export default content | |||||
| } | |||||
| declare module '*.module.scss' { | |||||
| const content: any | |||||
| export default content | |||||
| export const stylesheet: string | |||||
| } | |||||
| declare module '*.module.css' { | |||||
| const content: any | |||||
| export default content | |||||
| export const stylesheet: string | |||||
| } | |||||
| declare module '*.css' { | |||||
| const content: string | |||||
| export default content | |||||
| } | |||||
| // export {} | |||||
| // hack for typedoc | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| // declare type OffscreenCanvas = HTMLCanvasElement |
| export {BlendLoadPlugin} from './BlendLoadPlugin' |
| MIT License | |||||
| Copyright (c) 2020 Anthony C, Weathersby | |||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| of this software and associated documentation files (the "Software"), to deal | |||||
| in the Software without restriction, including without limitation the rights | |||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| copies of the Software, and to permit persons to whom the Software is | |||||
| furnished to do so, subject to the following conditions: | |||||
| The above copyright notice and this permission notice shall be included in all | |||||
| copies or substantial portions of the Software. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
| SOFTWARE. |
| export function parseBlend(arrayBuffer: ArrayBuffer): Promise<any> |
| /* eslint-disable camelcase */ | |||||
| /** | |||||
| * JS.Blend | |||||
| * Original Repo: https://github.com/acweathersby/js.blend | |||||
| * Slightly modified for three.js and js updates, minor refactor. | |||||
| * Object-creation part re-written for latest three.js and typescript | |||||
| * MIT License | |||||
| * Copyright (c) 2020 Anthony C, Weathersby | |||||
| * @license | |||||
| */ | |||||
| import parser from './parser/parser.js'; | |||||
| export async function parseBlend (buffer, name = '') { | |||||
| return new Promise((res, rej) => { | |||||
| parser.onParseReady = (file, error) => { | |||||
| if (error) rej(error) | |||||
| else res(file) | |||||
| } | |||||
| parser.loadBlendFromArrayBuffer(buffer, name) | |||||
| }) | |||||
| } |
| /* eslint-disable camelcase, no-unused-vars, no-empty, no-constant-condition */ | |||||
| const DNA1 = 826363460; | |||||
| const ENDB = 1111772741; | |||||
| /* Note: Blender coordinates treat the Z axis as the vertical an Y as depth. */ | |||||
| // web worker not functional in this version | |||||
| let USE_WEBWORKER = false; | |||||
| let worker = null; | |||||
| // FR = new FileReader(), | |||||
| const return_object = { | |||||
| loadBlendFromArrayBuffer: function (array_buffer) { | |||||
| return_object.ready = false; | |||||
| if (USE_WEBWORKER) { | |||||
| worker.postMessage(array_buffer, array_buffer); | |||||
| } else { | |||||
| worker.onmessage({ | |||||
| data: array_buffer, | |||||
| }); | |||||
| } | |||||
| }, | |||||
| // loadBlendFromBlob: function (blob) { | |||||
| // FR.onload = function () { | |||||
| // return_object.loadBlendFromArrayBuffer(this.result); | |||||
| // }; | |||||
| // FR.readAsArrayBuffer(blob); | |||||
| // }, | |||||
| ready: true, | |||||
| onParseReady: function () {}, | |||||
| }; | |||||
| function worker_code () { | |||||
| 'use strict'; | |||||
| let data = null, | |||||
| _data = null, | |||||
| BIG_ENDIAN = false, | |||||
| pointer_size = 0, | |||||
| struct_names = [], | |||||
| offset = 0, | |||||
| working_blend_file = null, | |||||
| current_SDNA_template = null, | |||||
| templates = {}, | |||||
| finished_objects = [], | |||||
| FILE = null, | |||||
| ERROR = null, | |||||
| AB = null; | |||||
| const self = this; | |||||
| function parseFile (msg) { | |||||
| if (typeof msg.data == 'object') { | |||||
| // reset global variables | |||||
| AB = null; | |||||
| data = null; | |||||
| BIG_ENDIAN = false; | |||||
| pointer_size = 0; | |||||
| struct_names = []; | |||||
| offset = 0; | |||||
| working_blend_file = null; | |||||
| finished_objects = []; | |||||
| current_SDNA_template = null; | |||||
| // set data | |||||
| _data = msg.data; | |||||
| AB = _data.slice(); | |||||
| data = new DataView(_data); | |||||
| FILE = new BLENDER_FILE(AB); | |||||
| // start parsing | |||||
| readFile(); | |||||
| // export parsed data | |||||
| self.postMessage(FILE, ERROR); | |||||
| } | |||||
| } | |||||
| /* | |||||
| Export object for a parsed __blender_file__. | |||||
| */ | |||||
| var BLENDER_FILE = function (AB) { | |||||
| this.AB = AB; | |||||
| // this.double = new Float64Array(AB); | |||||
| this.byte = new Uint8Array(AB); | |||||
| this.dv = new DataView(AB); | |||||
| this.objects = {}; | |||||
| this.memory_lookup = {}, | |||||
| this.object_array = []; | |||||
| this.template = null; | |||||
| }; | |||||
| BLENDER_FILE.prototype = { | |||||
| addObject: function (obj) { | |||||
| this.object_array.push(obj); | |||||
| if (!this.objects[obj.blender_name]) this.objects[obj.blender_name] = []; | |||||
| this.objects[obj.blender_name].push(obj); | |||||
| }, | |||||
| getPointer: function (offset) { | |||||
| const pointerLow = this.dv.getUint32(offset, this.template.endianess); | |||||
| if (this.template.pointer_size > 4) { | |||||
| const pointerHigh = this.dv.getUint32(offset + 4, this.template.endianess); | |||||
| if (this.template.endianess) { | |||||
| return (pointerLow) + 'l|h' + pointerHigh; | |||||
| } else { | |||||
| return (pointerHigh) + 'h|l' + pointerLow; | |||||
| } | |||||
| } else { | |||||
| return pointerLow; | |||||
| } | |||||
| }, | |||||
| }; | |||||
| self.onmessage = parseFile; | |||||
| // this.onmessage = parseFile; | |||||
| /* | |||||
| These functions map offsets in the blender __blender_file__ to basic types (byte,short,int,float) through TypedArrays; | |||||
| This allows the underlying binary data to be changed. | |||||
| */ | |||||
| function float64Prop (offset, Blender_Array_Length, length) { | |||||
| return { | |||||
| get: function () { | |||||
| return (Blender_Array_Length > 1) ? | |||||
| new Float64Array(this.__blender_file__.AB, this.__data_address__ + offset, length) : | |||||
| this.__blender_file__.dv.getFloat64(this.__data_address__ + offset, this.__blender_file__.template.endianess); | |||||
| }, | |||||
| set: function (float) { | |||||
| if (Blender_Array_Length > 1) {} else { | |||||
| this.__blender_file__.dv.setFloat64(this.__data_address__ + offset, float, this.__blender_file__.template.endianess); | |||||
| } | |||||
| }, | |||||
| }; | |||||
| } | |||||
| function floatProp (offset, Blender_Array_Length, length) { | |||||
| return { | |||||
| get: function () { | |||||
| return (Blender_Array_Length > 1) ? | |||||
| new Float32Array(this.__blender_file__.AB, this.__data_address__ + offset, length) : | |||||
| this.__blender_file__.dv.getFloat32(this.__data_address__ + offset, this.__blender_file__.template.endianess); | |||||
| }, | |||||
| set: function (float) { | |||||
| if (Blender_Array_Length > 1) {} else { | |||||
| this.__blender_file__.dv.setFloat32(this.__data_address__ + offset, float, this.__blender_file__.template.endianess); | |||||
| } | |||||
| }, | |||||
| }; | |||||
| } | |||||
| function intProp (offset, Blender_Array_Length, length) { | |||||
| return { | |||||
| get: function () { | |||||
| return (Blender_Array_Length > 1) ? | |||||
| new Int32Array(this.__blender_file__.AB, this.__data_address__ + offset, length) : | |||||
| this.__blender_file__.dv.getInt32(this.__data_address__ + offset, this.__blender_file__.template.endianess); | |||||
| }, | |||||
| set: function (float) { | |||||
| if (Blender_Array_Length > 1) {} else { | |||||
| this.__blender_file__.dv.setInt32(this.__data_address__ + offset, float, this.__blender_file__.template.endianess); | |||||
| } | |||||
| }, | |||||
| }; | |||||
| } | |||||
| function uIntProp (offset, Blender_Array_Length, length) { | |||||
| return { | |||||
| get: function () { | |||||
| return (Blender_Array_Length > 1) ? | |||||
| new Uint32Array(this.__blender_file__.AB, this.__data_address__ + offset, length) : | |||||
| this.__blender_file__.dv.getUint32(this.__data_address__ + offset, this.__blender_file__.template.endianess); | |||||
| }, | |||||
| set: function (float) { | |||||
| if (Blender_Array_Length > 1) {} else { | |||||
| this.__blender_file__.dv.setUint32(this.__data_address__ + offset, float, this.__blender_file__.template.endianess); | |||||
| } | |||||
| }, | |||||
| }; | |||||
| } | |||||
| function shortProp (offset, Blender_Array_Length, length) { | |||||
| return { | |||||
| get: function () { | |||||
| return (Blender_Array_Length > 1) ? | |||||
| new Int16Array(this.__blender_file__.AB, this.__data_address__ + offset, length) : | |||||
| this.__blender_file__.dv.getInt16(this.__data_address__ + offset, this.__blender_file__.template.endianess); | |||||
| }, | |||||
| set: function (float) { | |||||
| if (Blender_Array_Length > 1) {} else { | |||||
| this.__blender_file__.dv.setInt16(this.__data_address__ + offset, float, this.__blender_file__.template.endianess); | |||||
| } | |||||
| }, | |||||
| }; | |||||
| } | |||||
| const uShortProp = (offset, Blender_Array_Length, length) => { | |||||
| return { | |||||
| get: function () { | |||||
| return (Blender_Array_Length > 1) ? | |||||
| new Uint16Array(this.__blender_file__.AB, this.__data_address__ + offset, length) : | |||||
| this.__blender_file__.dv.getUint16(this.__data_address__ + offset, this.__blender_file__.template.endianess); | |||||
| }, | |||||
| set: function (float) { | |||||
| if (Blender_Array_Length > 1) { | |||||
| } else { | |||||
| this.__blender_file__.dv.setUint16(this.__data_address__ + offset, float, this.__blender_file__.template.endianess); | |||||
| } | |||||
| }, | |||||
| }; | |||||
| }; | |||||
| function charProp (offset, Blender_Array_Length, length) { | |||||
| return { | |||||
| get: function () { | |||||
| if (Blender_Array_Length > 1) { | |||||
| let start = this.__data_address__ + offset; | |||||
| let end = start; | |||||
| let buffer_guard = 0; | |||||
| while (this.__blender_file__.byte[end] != 0 && buffer_guard++ < length) end++; | |||||
| return toString(this.__blender_file__.AB, start, end); | |||||
| } | |||||
| return this.__blender_file__.byte[(this.__data_address__ + offset)]; | |||||
| }, | |||||
| set: function (byte) { | |||||
| if (Blender_Array_Length > 1) { | |||||
| const string = byte + ''; | |||||
| let i = 0; | |||||
| const l = string.length; | |||||
| while (i < length) { | |||||
| if (i < l) { | |||||
| this.__blender_file__.byte[(this.__data_address__ + offset + i)] = string.charCodeAt(i) | 0; | |||||
| } else { | |||||
| this.__blender_file__.byte[(this.__data_address__ + offset + i)] = 0; | |||||
| } | |||||
| i++; | |||||
| } | |||||
| } else { | |||||
| this.__blender_file__.byte[(this.__data_address__ + offset)] = byte | 0; | |||||
| } | |||||
| }, | |||||
| }; | |||||
| } | |||||
| function pointerProp2 (offset) { | |||||
| return { | |||||
| get: function () { | |||||
| let pointer = this.__blender_file__.getPointer(this.__data_address__ + offset, this.__blender_file__); | |||||
| const link = this.__blender_file__.memory_lookup[pointer]; | |||||
| const results = []; | |||||
| if (link) { | |||||
| const address = link.__data_address__; | |||||
| let j = 0; | |||||
| while (true) { | |||||
| pointer = this.__blender_file__.getPointer(address + j * 8, this.__blender_file__); | |||||
| let obj = this.__blender_file__.memory_lookup[pointer]; | |||||
| if (!obj) break; | |||||
| results.push(obj); | |||||
| j++; | |||||
| } | |||||
| } | |||||
| return results; | |||||
| }, | |||||
| set: function () {}, | |||||
| }; | |||||
| } | |||||
| function pointerProp (offset, Blender_Array_Length, length) { | |||||
| return { | |||||
| get: function () { | |||||
| if (Blender_Array_Length > 1) { | |||||
| let array = []; | |||||
| let j = 0; | |||||
| let off = offset; | |||||
| while (j < Blender_Array_Length) { | |||||
| let pointer = this.__blender_file__.getPointer(this.__data_address__ + off, this.__blender_file__); | |||||
| array.push(this.__blender_file__.memory_lookup[pointer]); | |||||
| off += length; | |||||
| j++; | |||||
| } | |||||
| return array; | |||||
| } else { | |||||
| let pointer = this.__blender_file__.getPointer(this.__data_address__ + offset, this.__blender_file__); | |||||
| return this.__blender_file__.memory_lookup[pointer]; | |||||
| } | |||||
| }, | |||||
| set: function () {}, | |||||
| }; | |||||
| } | |||||
| function compileProp (obj, name, type, offset, array_size, IS_POINTER, pointer_size, length) { | |||||
| if (!IS_POINTER) { | |||||
| switch (type) { | |||||
| case 'double': | |||||
| Object.defineProperty(obj, name, float64Prop(offset, array_size, length >> 3)); | |||||
| break; | |||||
| case 'float': | |||||
| Object.defineProperty(obj, name, floatProp(offset, array_size, length >> 2)); | |||||
| break; | |||||
| case 'int': | |||||
| Object.defineProperty(obj, name, intProp(offset, array_size, length >> 2)); | |||||
| break; | |||||
| case 'short': | |||||
| case 'ushort': | |||||
| Object.defineProperty(obj, name, shortProp(offset, array_size, length >> 1)); | |||||
| break; | |||||
| case 'char': | |||||
| case 'uchar': | |||||
| Object.defineProperty(obj, name, charProp(offset, array_size, length)); | |||||
| break; | |||||
| default: | |||||
| // compile list to | |||||
| obj[name] = {}; | |||||
| obj.__list__.push(name, type, length, offset, array_size, IS_POINTER); | |||||
| } | |||||
| obj._length += length; | |||||
| offset += length; | |||||
| } else { | |||||
| Object.defineProperty(obj, name, pointerProp(offset, array_size, pointer_size)); | |||||
| offset += pointer_size * array_size; | |||||
| } | |||||
| return offset; | |||||
| } | |||||
| // Store final DNA structs | |||||
| const MASTER_SDNA_SCHEMA = function (version) { | |||||
| this.version = version; | |||||
| this.SDNA_SET = false; | |||||
| this.byte_size = 0; | |||||
| this.struct_index = 0; | |||||
| this.structs = {}; | |||||
| this.SDNA = {}; | |||||
| this.endianess = false; | |||||
| }; | |||||
| MASTER_SDNA_SCHEMA.prototype = { | |||||
| getSDNAStructureConstructor: function (name, struct) { | |||||
| if (struct) { | |||||
| const blen_struct = Function('function ' + name + '(){}; return ' + name)(); | |||||
| blen_struct.prototype = new BLENDER_STRUCTURE(); | |||||
| blen_struct.prototype.blender_name = name; | |||||
| blen_struct.prototype.__pointers = []; | |||||
| blen_struct.prototype.__list__ = []; | |||||
| let offset = 0; | |||||
| // Create properties of struct | |||||
| for (let i = 0; i < struct.length; i += 3) { | |||||
| let _name = struct[i]; | |||||
| const n = _name, | |||||
| type = struct[i + 1]; | |||||
| let length = struct[i + 2], | |||||
| array_length = 0, | |||||
| match = null, | |||||
| Blender_Array_Length = 1, | |||||
| Suparray_match = 1, | |||||
| PointerToArray = false, | |||||
| Pointer_Match = 0; | |||||
| const DNA = this.SDNA[name] = { | |||||
| constructor: blen_struct, | |||||
| }; | |||||
| let original_name = _name; | |||||
| // mini type parser | |||||
| if ((match = _name.match(/(\*?)(\*?)(\w+)(\[(\w*)\])?(\[(\w*)\])?/))) { | |||||
| // base name | |||||
| _name = match[3]; | |||||
| // pointer type | |||||
| if (match[1]) { | |||||
| Pointer_Match = 10; | |||||
| blen_struct.prototype.__pointers.push(_name); | |||||
| } | |||||
| if (match[2]) { | |||||
| PointerToArray = true; | |||||
| } | |||||
| // arrays | |||||
| if (match[4]) { | |||||
| if (match[6]) { | |||||
| Suparray_match = parseInt(match[5]); | |||||
| Blender_Array_Length = parseInt(match[7]); | |||||
| } else { | |||||
| Blender_Array_Length = parseInt(match[5]); | |||||
| } | |||||
| } | |||||
| array_length = Blender_Array_Length * length; | |||||
| length = array_length * Suparray_match; | |||||
| } | |||||
| DNA[n] = { | |||||
| type: type, | |||||
| length: length, | |||||
| isArray: (Blender_Array_Length > 0), | |||||
| }; | |||||
| if (PointerToArray) { | |||||
| Object.defineProperty(blen_struct.prototype, _name, pointerProp2(offset)); | |||||
| offset += pointer_size; | |||||
| } else if (Suparray_match > 1) { | |||||
| const array_names = new Array(Suparray_match); | |||||
| // construct sub_array object that will return the correct structs | |||||
| for (let j = 0; j < Suparray_match; j++) { | |||||
| let array_name_ = `__${_name}[${j}]__`; | |||||
| array_names[j] = array_name_; | |||||
| offset = compileProp(blen_struct.prototype, array_name_, type, offset, Blender_Array_Length, Pointer_Match, pointer_size, array_length); | |||||
| } | |||||
| Object.defineProperty(blen_struct.prototype, _name, { | |||||
| get: (function (array_names) { | |||||
| return function () { | |||||
| const array = []; | |||||
| for (let i = 0; i < array_names.length; i++) { | |||||
| array.push(this[array_names[i]]); | |||||
| } | |||||
| return array; | |||||
| }; | |||||
| })(array_names), | |||||
| }); | |||||
| } else { | |||||
| offset = compileProp(blen_struct.prototype, _name, type, offset, Blender_Array_Length, Pointer_Match, pointer_size, length); | |||||
| } | |||||
| } | |||||
| return this.SDNA[name].constructor; | |||||
| } else { | |||||
| if (!this.SDNA[name]) { | |||||
| return null; | |||||
| } | |||||
| return this.SDNA[name].constructor; | |||||
| } | |||||
| }, | |||||
| }; | |||||
| var BLENDER_STRUCTURE = function () { | |||||
| this.__blender_file__ = null; | |||||
| this.__list__ = null; | |||||
| this.__super_array_list__ = null; | |||||
| this.blender_name = ''; | |||||
| this.__pointers = null; | |||||
| this.address = null; | |||||
| this.length = 0; | |||||
| this.__data_address__ = 0; | |||||
| this.blender_name = ''; | |||||
| this._length = 0; | |||||
| }; | |||||
| /* | |||||
| Returns a pre-constructed BLENDER_STRUCTURE or creates a new BLENDER_STRUCTURE to match the DNA struct type | |||||
| */ | |||||
| const pointer_function = (pointer) => () => { | |||||
| return FILE.memory_lookup[pointer]; | |||||
| }; | |||||
| function getPointer (offset) { | |||||
| const pointerLow = data.getUint32(offset, BIG_ENDIAN); | |||||
| if (pointer_size > 4) { | |||||
| const pointerHigh = data.getUint32(offset + 4, BIG_ENDIAN); | |||||
| if (BIG_ENDIAN) { | |||||
| return (pointerLow) + '' + pointerHigh; | |||||
| } else { | |||||
| return (pointerHigh) + '' + pointerLow; | |||||
| } | |||||
| } else { | |||||
| return pointerLow; | |||||
| } | |||||
| } | |||||
| BLENDER_STRUCTURE.prototype = { | |||||
| setData: function (pointer, _data_offset, data_block_length, BLENDER_FILE) { | |||||
| if (this.__list__ === null) return this; | |||||
| BLENDER_FILE.addObject(this); | |||||
| this.__blender_file__ = BLENDER_FILE; | |||||
| const struct = this.__list__; | |||||
| let j = 0, | |||||
| i = 0, | |||||
| obj, name = '', | |||||
| type, length, Blender_Array_Length, Pointer_Match, offset, constructor; | |||||
| this.__data_address__ = _data_offset; | |||||
| if (struct === null) return this; | |||||
| for (i = 0; i < struct.length; i += 6) { | |||||
| obj = null; | |||||
| name = struct[i]; | |||||
| type = struct[i + 1]; | |||||
| Blender_Array_Length = struct[i + 4]; | |||||
| Pointer_Match = struct[i + 5]; | |||||
| offset = this.__data_address__ + struct[i + 3]; | |||||
| if (Blender_Array_Length > 1) { | |||||
| this[name] = []; | |||||
| j = 0; | |||||
| while (j < Blender_Array_Length) { | |||||
| if (current_SDNA_template.getSDNAStructureConstructor(type)) { | |||||
| constructor = current_SDNA_template.getSDNAStructureConstructor(type); | |||||
| this[name].push((new constructor()).setData(0, offset, offset + length / Blender_Array_Length, BLENDER_FILE)); | |||||
| } else this[name].push(null); | |||||
| offset += length / Blender_Array_Length; | |||||
| j++; | |||||
| } | |||||
| } else { | |||||
| if (current_SDNA_template.getSDNAStructureConstructor(type)) { | |||||
| constructor = current_SDNA_template.getSDNAStructureConstructor(type); | |||||
| this[name] = (new constructor()).setData(0, offset, length + offset, BLENDER_FILE); | |||||
| } else this[name] = null; | |||||
| } | |||||
| } | |||||
| // break connection to configuration list | |||||
| this.__list__ = null; | |||||
| return this; | |||||
| }, | |||||
| get aname () { | |||||
| if (this.id) return this.id.name.slice(2); | |||||
| else return undefined; | |||||
| }, | |||||
| }; | |||||
| function toString (buffer, _in, _out) { | |||||
| return String.fromCharCode.apply(String, new Uint8Array(buffer, _in, _out - _in)); | |||||
| } | |||||
| // Begin parsing blender __blender_file__ | |||||
| function readFile () { | |||||
| let count = 0; | |||||
| let offset2 = 0; | |||||
| const root = 0; | |||||
| const i = 0; | |||||
| let data_offset = 0; | |||||
| let sdna_index = 0; | |||||
| let code = ''; | |||||
| let block_length = 0; | |||||
| let curr_count = 0; | |||||
| let curr_count2 = 0; | |||||
| FILE.memory_lookup = {}; | |||||
| struct_names = []; | |||||
| offset = 0; | |||||
| // Make sure we have a .blend __blender_file__. All blend files have the first 12bytes | |||||
| // set with BLENDER-v### in Utf-8 | |||||
| if (toString(_data, offset, 7) !== 'BLENDER') return ERROR = 'File supplied is not a .blend compatible Blender file.'; | |||||
| // otherwise get templete from save version. | |||||
| offset += 7; | |||||
| pointer_size = ((toString(_data, offset++, offset)) == '_') ? 4 : 8; | |||||
| BIG_ENDIAN = toString(_data, offset++, offset) !== 'V'; | |||||
| const version = toString(_data, offset, offset + 3); | |||||
| // create new master template if none exist for current blender version; | |||||
| if (!templates[version]) { | |||||
| templates[version] = new MASTER_SDNA_SCHEMA(version); | |||||
| } | |||||
| current_SDNA_template = templates[version]; | |||||
| FILE.template = current_SDNA_template; | |||||
| offset += 3; | |||||
| // Set SDNA structs if template hasn't been set. | |||||
| // Todo: Move the following block into the MASTER_SDNA_SCHEMA object. | |||||
| //* Like so:*/ current_SDNA_template.set(AB); | |||||
| if (!current_SDNA_template.SDNA_SET) { | |||||
| current_SDNA_template.endianess = BIG_ENDIAN; | |||||
| current_SDNA_template.pointer_size = pointer_size; | |||||
| // find DNA1 data block | |||||
| offset2 = offset; | |||||
| while (true) { | |||||
| sdna_index = data.getInt32(offset2 + pointer_size + 8, BIG_ENDIAN); | |||||
| // eslint-disable-next-line no-control-regex | |||||
| code = toString(_data, offset2, offset2 + 4).replace(/\u0000/g, ''); | |||||
| block_length = data.getInt32(offset2 + 4, true); | |||||
| offset2 += 16 + (pointer_size); | |||||
| if (code === 'DNA1') { | |||||
| // DNA found; This is the core of the __blender_file__ and contains all the structure for the various data types used in Blender. | |||||
| count = 0; | |||||
| let types = [], | |||||
| fields = [], | |||||
| names = [], | |||||
| lengths = [], | |||||
| name = '', | |||||
| curr_name = ''; | |||||
| // skip SDNA and NAME identifiers | |||||
| offset2 += 8; | |||||
| // Number of structs. | |||||
| count = data.getInt32(offset2, true); | |||||
| offset2 += 4; | |||||
| curr_count = 0; | |||||
| // Build up list of names for structs | |||||
| while (curr_count < count) { | |||||
| curr_name = ''; | |||||
| while (data.getInt8(offset2) !== 0) { | |||||
| curr_name += toString(_data, offset2, offset2 + 1); | |||||
| offset2++; | |||||
| } | |||||
| names.push(curr_name); | |||||
| offset2++; | |||||
| curr_count++; | |||||
| } | |||||
| // Adjust for 4byte alignment | |||||
| if ((offset2 % 4) > 0) offset2 = (4 - (offset2 % 4)) + offset2; | |||||
| offset2 += 4; | |||||
| // Number of struct types | |||||
| count = data.getInt32(offset2, true); | |||||
| offset2 += 4; | |||||
| curr_count = 0; | |||||
| // Build up list of types | |||||
| while (curr_count < count) { | |||||
| curr_name = ''; | |||||
| while (data.getInt8(offset2) !== 0) { | |||||
| curr_name += toString(_data, offset2, offset2 + 1); | |||||
| offset2++; | |||||
| } | |||||
| types.push(curr_name); | |||||
| offset2++; | |||||
| curr_count++; | |||||
| } | |||||
| // Adjust for 4byte alignment | |||||
| if ((offset2 % 4) > 0) offset2 = (4 - (offset2 % 4)) + offset2; | |||||
| offset2 += 4; | |||||
| curr_count = 0; | |||||
| // Build up list of byte lengths for types | |||||
| while (curr_count < count) { | |||||
| lengths.push(data.getInt16(offset2, BIG_ENDIAN)); | |||||
| offset2 += 2; | |||||
| curr_count++; | |||||
| } | |||||
| // Adjust for 4byte alignment | |||||
| if ((offset2 % 4) > 0) offset2 = (4 - (offset2 % 4)) + offset2; | |||||
| offset2 += 4; | |||||
| // Number of structures | |||||
| const structure_count = data.getInt32(offset2, BIG_ENDIAN); | |||||
| offset2 += 4; | |||||
| curr_count = 0; | |||||
| // Create constructor objects from list of SDNA structs | |||||
| while (curr_count < structure_count) { | |||||
| const struct_name = types[data.getInt16(offset2, BIG_ENDIAN)]; | |||||
| offset2 += 2; | |||||
| const obj = []; | |||||
| count = data.getInt16(offset2, BIG_ENDIAN); | |||||
| offset2 += 2; | |||||
| curr_count2 = 0; | |||||
| struct_names.push(struct_name); | |||||
| // Fill an array with name, type, and length for each SDNA struct property | |||||
| while (curr_count2 < count) { | |||||
| obj.push(names[data.getInt16(offset2 + 2, BIG_ENDIAN)], types[data.getInt16(offset2, BIG_ENDIAN)], lengths[data.getInt16(offset2, BIG_ENDIAN)]); | |||||
| offset2 += 4; | |||||
| curr_count2++; | |||||
| } | |||||
| // Create a SDNA constructor by passing [type,name,lenth] array as second argument | |||||
| current_SDNA_template.getSDNAStructureConstructor(struct_name, obj); | |||||
| curr_count++; | |||||
| } | |||||
| current_SDNA_template.SDNA_SET = true; | |||||
| current_SDNA_template.SDNA_NAMES = struct_names; | |||||
| break; | |||||
| } | |||||
| offset2 += block_length; | |||||
| } | |||||
| } | |||||
| // parse the rest of the data, starting back at the top. | |||||
| // TODO: turn into "on-demand" parsing. | |||||
| while (true) { | |||||
| if ((offset % 4) > 0) { | |||||
| offset = (4 - (offset % 4)) + offset; | |||||
| } | |||||
| data_offset = offset; | |||||
| sdna_index = data.getInt32(offset + pointer_size + 8, BIG_ENDIAN); | |||||
| let code_uint = data.getUint32(offset, BIG_ENDIAN); | |||||
| offset2 = offset + 16 + (pointer_size); | |||||
| offset += data.getInt32(offset + 4, true) + 16 + (pointer_size); | |||||
| if (code_uint === DNA1); // skip - already processed at this point | |||||
| else if (code_uint === ENDB) break; // end of __blender_file__ found | |||||
| else { | |||||
| // Create a Blender object using a constructor template from current_SDNA_template | |||||
| const data_start = data_offset + pointer_size + 16; | |||||
| // Get a SDNA constructor by name; | |||||
| const constructor = current_SDNA_template.getSDNAStructureConstructor(current_SDNA_template.SDNA_NAMES[sdna_index]); | |||||
| const size = data.getInt32(data_offset + 4, BIG_ENDIAN); | |||||
| count = data.getInt32(data_offset + 12 + pointer_size, BIG_ENDIAN); | |||||
| if (count > 0) { | |||||
| let obj = new constructor(); | |||||
| const length = constructor.prototype._length; | |||||
| const address = FILE.getPointer(data_offset + 8); | |||||
| obj.address = address + ''; | |||||
| obj.setData(address, data_start, data_start + size, FILE); | |||||
| if (count > 1) { | |||||
| let array = []; | |||||
| array.push(obj); | |||||
| for (let u = 1; u < count; u++) { | |||||
| obj = new constructor(); | |||||
| obj.setData(address, data_start + length * u, data_start + (length * u) + length, FILE); | |||||
| array.push(obj); | |||||
| } | |||||
| FILE.memory_lookup[address] = array; | |||||
| } else { | |||||
| FILE.memory_lookup[address] = obj; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| worker = new worker_code(); | |||||
| worker.postMessage = function (message) { | |||||
| return_object.onParseReady(message); | |||||
| }; | |||||
| export default return_object; |
| /* eslint-disable camelcase, no-unused-vars, no-empty, no-constant-condition */ | |||||
| import {createThreeJSBufferGeometry} from './mesh.js'; | |||||
| import {createThreeJSMaterial} from './material.js'; | |||||
| import {Mesh} from 'threepipe'; | |||||
| import {createThreeJSLamp} from './light.js'; | |||||
| const blender_object_types = { | |||||
| mesh: 1, | |||||
| lamp: 10, | |||||
| }; | |||||
| function createObject (blender_file, object) { | |||||
| if (object.data) { | |||||
| // get the mesh | |||||
| const buffered_geometry = createThreeJSBufferGeometry(object.data, [0, 0, 0]); | |||||
| console.log(object) | |||||
| const blend_material = object.data.mat[0]; | |||||
| const material = blend_material ? createThreeJSMaterial(blend_material) : null; | |||||
| const mesh = new Mesh(buffered_geometry, material); | |||||
| mesh.castShadow = true; | |||||
| mesh.receiveShadow = true; | |||||
| mesh.rotateZ(object.rot[2]); | |||||
| mesh.rotateY(object.rot[1]); | |||||
| mesh.rotateX(object.rot[0]); | |||||
| mesh.scale.fromArray(object.size, 0); | |||||
| mesh.position.fromArray([object.loc[0], (object.loc[2]), (-object.loc[1])], 0); | |||||
| return mesh; | |||||
| } | |||||
| return null; | |||||
| } | |||||
| function loadObject (object_name, blender_file, cache) { | |||||
| const objects = blender_file.Object; | |||||
| for (let i = 0; i < objects.length; i++) { | |||||
| let object = objects[i]; | |||||
| if (object.aname === object_name) { | |||||
| switch (object.type) { | |||||
| case blender_object_types.mesh: | |||||
| return createObject(object, blender_file); | |||||
| case blender_object_types.lamp: | |||||
| return createThreeJSLamp(object, blender_file); | |||||
| default: | |||||
| console.warn('Unsupported object type', object.type); | |||||
| } | |||||
| } | |||||
| } | |||||
| return null; | |||||
| } | |||||
| function loadScene (three_scene, blender_file, cache) { | |||||
| for (let i = 0; i < blender_file.objects.Object.length; i++) { | |||||
| let object = blender_file.objects.Object[i]; | |||||
| // Load Lights | |||||
| if (object.type === blender_object_types.lamp) { | |||||
| let light = createThreeJSLamp(object, blender_file); | |||||
| three_scene.add(light); | |||||
| } | |||||
| // Load Meshes | |||||
| if (object.type === blender_object_types.mesh) { | |||||
| let mesh = createObject(blender_file, object); | |||||
| if(mesh) { | |||||
| three_scene.add(mesh); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| export default function (blender_file) { | |||||
| const cache = {}; | |||||
| return { | |||||
| loadScene: (three_scene) => loadScene(three_scene, blender_file, cache), | |||||
| loadObject: (object_name) => loadObject(object_name, blender_file, cache), | |||||
| }; | |||||
| } |
| /* eslint-disable camelcase, no-unused-vars, no-empty, no-constant-condition */ | |||||
| import {PointLight} from 'threepipe'; | |||||
| const blender_light_types = { | |||||
| point: 0, | |||||
| sun: 1, | |||||
| spot: 0, | |||||
| hemi: 0, | |||||
| area: 0, | |||||
| }; | |||||
| export function createThreeJSLamp (blend_lamp) { | |||||
| console.log(blend_lamp) | |||||
| let ldata = blend_lamp.data; | |||||
| let pos_array = [blend_lamp.loc[0], blend_lamp.loc[2], -blend_lamp.loc[1]]; | |||||
| let color = ((ldata.r * 255) << 16) | ((ldata.g * 255) << 8) | ((ldata.b * 255) << 0); | |||||
| // let intesity = 20; | |||||
| let intesity = ldata.energy; | |||||
| let distance = 0; | |||||
| let three_light = null; | |||||
| switch (ldata.type) { | |||||
| case blender_light_types.point: | |||||
| three_light = new PointLight(color, intesity, distance); | |||||
| three_light.position.fromArray(pos_array, 0); | |||||
| three_light.castShadow = true; | |||||
| break; | |||||
| case blender_light_types.sun: | |||||
| three_light = new PointLight(color, intesity, distance); | |||||
| three_light.position.fromArray(pos_array, 0); | |||||
| three_light.castShadow = true; | |||||
| three_light.shadow.mapSize.width = 1024; | |||||
| three_light.shadow.mapSize.height = 1024; | |||||
| three_light.shadow.camera.near = 0.01; | |||||
| three_light.shadow.camera.far = 500; | |||||
| break; | |||||
| default: | |||||
| console.warn('Unsupported light type', ldata.type); | |||||
| } | |||||
| return three_light; | |||||
| } |
| /* eslint-disable camelcase, no-unused-vars, no-empty, no-constant-condition */ | |||||
| import {createThreeJSTexture} from './texture.js'; | |||||
| import {CubeReflectionMapping, EquirectangularReflectionMapping, MeshPhysicalMaterial} from 'threepipe'; | |||||
| const texture_mappings = { | |||||
| diff_color: 1, | |||||
| normal: 2, | |||||
| mirror: 8, | |||||
| diff_intensity: 16, | |||||
| spec_intensity: 32, | |||||
| emit: 32, | |||||
| alpha: 128, | |||||
| spec_hardness: 256, | |||||
| ray_mirror: 512, | |||||
| translucency: 1024, | |||||
| ambient: 2048, | |||||
| displacement: 4096, | |||||
| warp: 8192, | |||||
| }; | |||||
| let blender_specular_types = { | |||||
| cooktorr: 0, | |||||
| phong: 1, | |||||
| blinn: 2, | |||||
| toon: 3, | |||||
| wardiso: 4, | |||||
| }; | |||||
| function applyColorMapping (blender_texture, three_texture, material) { | |||||
| if (blender_texture.mapto & texture_mappings.diff_color) { | |||||
| material.map = three_texture; | |||||
| } | |||||
| } | |||||
| function applySpecMapping (blender_texture, three_texture, material) { | |||||
| if (blender_texture.mapto & texture_mappings.spec_color && material.type != 'MeshStandardMaterial') { | |||||
| material.specularMap = three_texture; | |||||
| } | |||||
| if (blender_texture.mapto & texture_mappings.spec_intensity && material.type != 'MeshStandardMaterial') { | |||||
| material.roughnessMap = three_texture; | |||||
| } | |||||
| } | |||||
| function applyAlphaMapping (blender_texture, three_texture, material) { | |||||
| if (blender_texture.mapto & texture_mappings.alpha) { | |||||
| material.alphaMap = three_texture; | |||||
| } | |||||
| } | |||||
| function applyNormalMapping (blender_texture, three_texture, material) { | |||||
| if (blender_texture.mapto & texture_mappings.normal) { | |||||
| material.normalMap = three_texture; | |||||
| material.normalScale = { | |||||
| x: blender_texture.norfac, | |||||
| y: blender_texture.norfac, | |||||
| }; | |||||
| } | |||||
| } | |||||
| function applyMirrorMapping (blender_texture, three_texture, material) { | |||||
| if (blender_texture.mapto & texture_mappings.mirror) { | |||||
| material.envMap = three_texture; | |||||
| material.envMapIntensity = blender_texture.mirrfac; | |||||
| } | |||||
| } | |||||
| const blender_texture_coordinates = { | |||||
| GENERATED: 1, | |||||
| REFLECTION: 2, | |||||
| NORMAL: 4, | |||||
| GLOBAL: 8, | |||||
| UV: 16, | |||||
| OBJECT: 32, | |||||
| WINDOW: 1024, | |||||
| TANGENT: 4096, | |||||
| PARTICLE: 8192, | |||||
| STRESS: 16384, | |||||
| }; | |||||
| const blender_texture_mapping = { | |||||
| FLAT: 0, | |||||
| CUBE: 1, | |||||
| TUBE: 2, | |||||
| SPHERE: 3, | |||||
| }; | |||||
| function applyTexture (blender_texture, material) { | |||||
| // extract blender_texture data. Use Only if image has been supplied. | |||||
| if (blender_texture && blender_texture.tex && blender_texture.tex.ima) { | |||||
| let three_texture = createThreeJSTexture(blender_texture.tex.ima); | |||||
| if(blender_texture.texco == blender_texture_coordinates.REFLECTION) { | |||||
| switch(blender_texture.mapping) { | |||||
| case blender_texture_mapping.FLAT: | |||||
| three_texture.mapping = EquirectangularReflectionMapping; | |||||
| break; | |||||
| case blender_texture_mapping.SPHERE: | |||||
| three_texture.mapping = CubeReflectionMapping; | |||||
| break; | |||||
| default: break; | |||||
| } | |||||
| // three_texture.mapping = EquirectangularRefractionMapping; | |||||
| } | |||||
| applyColorMapping(blender_texture, three_texture, material); | |||||
| applySpecMapping(blender_texture, three_texture, material); | |||||
| applyAlphaMapping(blender_texture, three_texture, material); | |||||
| applyNormalMapping(blender_texture, three_texture, material); | |||||
| applyMirrorMapping(blender_texture, three_texture, material); | |||||
| } | |||||
| } | |||||
| export function createThreeJSMaterial (blend_mat) { | |||||
| console.log(blend_mat) | |||||
| let material = new MeshPhysicalMaterial(); | |||||
| material.color.setRGB(blend_mat.r, blend_mat.g, blend_mat.b); | |||||
| material.roughness = blend_mat.roughness !== undefined ? blend_mat.roughness : 0.4 | |||||
| material.metalness = blend_mat.metallic !== undefined ? blend_mat.metallic : 0.0 | |||||
| material.opacity = blend_mat.alpha !== undefined ? blend_mat.alpha : 0.0 | |||||
| material.tranparent = material.opacity < 1.0 | |||||
| // todo textures and nodes | |||||
| // const textures = blend_mat.mtex; | |||||
| // switch (blend_mat.spec_shader) { | |||||
| // case blender_specular_types.lambert: | |||||
| // material = new MeshLambertMaterial(); | |||||
| // material.color.setRGB(blend_mat.r, blend_mat.g, blend_mat.b); | |||||
| // break; | |||||
| // case blender_specular_types.blinn: | |||||
| // case blender_specular_types.phong: | |||||
| // | |||||
| // material = new MeshStandardMaterial(); | |||||
| // material.color.setRGB(blend_mat.r, blend_mat.g, blend_mat.b); | |||||
| // // material.specular.setRGB(blend_mat.specr, blend_mat.specg, blend_mat.specb); | |||||
| // material.roughness = (1 - (blend_mat.har / 512)); | |||||
| // material.metalness = 1 - blend_mat.ref; | |||||
| // if(blend_mat.alpha < 0.98) { | |||||
| // material.transparent = true; | |||||
| // material.opacity = blend_mat.alpha; | |||||
| // console.log(blend_mat, material) | |||||
| // } | |||||
| // break; | |||||
| // case blender_specular_types.wardiso: | |||||
| // case blender_specular_types.cooktorr: | |||||
| // material = new MeshPhongMaterial(); | |||||
| // material.color.setRGB(blend_mat.r, blend_mat.g, blend_mat.b); | |||||
| // material.specular.setRGB(blend_mat.specr, blend_mat.specg, blend_mat.specb); | |||||
| // material.shininess = blend_mat.har / 512; | |||||
| // material.reflectivity = blend_mat.ref * 100; | |||||
| // break; | |||||
| // default: | |||||
| // material = new MeshStandardMaterial(); | |||||
| // // material = new MeshLambertMaterial(); | |||||
| // material.color.setRGB(blend_mat.r, blend_mat.g, blend_mat.b); | |||||
| // material.roughness = 0.5; | |||||
| // material.metalness = 0; | |||||
| // break; | |||||
| // } | |||||
| // const at = (texture) => applyTexture(texture, material); | |||||
| // | |||||
| // if (textures && textures.length) textures.map(at); | |||||
| return material; | |||||
| } | |||||
| /* eslint-disable camelcase, no-unused-vars, no-empty, no-constant-condition */ | |||||
| import {BufferAttribute, BufferGeometry} from 'threepipe'; | |||||
| export function createBufferGeometry (blender_mesh, origin) { | |||||
| // get materials | |||||
| let pick_material = 0, | |||||
| mesh = blender_mesh, | |||||
| faces = mesh.mpoly, | |||||
| loops = mesh.mloop, | |||||
| UV = mesh.mloopuv, | |||||
| verts = mesh.mvert; | |||||
| const geometry = new BufferGeometry(); | |||||
| if (!faces) return geometry; | |||||
| let index_count = 0; | |||||
| // precalculate the size of the array needed for faces | |||||
| let face_indice_count = 0; | |||||
| let face_indice_counta = 0; | |||||
| for (let i = 0; i < faces.length; i++) { | |||||
| const face = faces[i] || faces; | |||||
| const len = face.totloop; | |||||
| let indexi = 1; | |||||
| face_indice_counta += (len * 2 / 3) | 0; | |||||
| while (indexi < len) { | |||||
| face_indice_count += 3; | |||||
| indexi += 2; | |||||
| } | |||||
| } | |||||
| // extract face info and dump into array buffer; | |||||
| const face_buffer = new Uint32Array(face_indice_count); | |||||
| const uv_buffer = new Float32Array(face_indice_count * 2); | |||||
| const normal_buffer = new Float32Array(face_indice_count * 3); | |||||
| const verts_array_buff = new Float32Array(face_indice_count * 3); | |||||
| for (let i = 0; i < faces.length; i++) { | |||||
| const face = faces[i] || faces; | |||||
| const len = face.totloop; | |||||
| const start = face.loopstart; | |||||
| let indexi = 1; | |||||
| const offset = 0; | |||||
| while (indexi < len) { | |||||
| const face_normals = []; | |||||
| const face_index_array = []; | |||||
| const face_uvs = []; | |||||
| let index = 0; | |||||
| for (let l = 0; l < 3; l++) { | |||||
| // Per Vertice | |||||
| if ((indexi - 1) + l < len) { | |||||
| index = start + (indexi - 1) + l; | |||||
| } else { | |||||
| index = start; | |||||
| } | |||||
| const v = loops[index].v; | |||||
| const vert = verts[v]; | |||||
| face_buffer[index_count] = index_count; | |||||
| // get normals, which are 16byte ints, and norm them back into floats. | |||||
| verts_array_buff[index_count * 3 + 0] = vert.co[0] + origin[0]; | |||||
| verts_array_buff[index_count * 3 + 1] = vert.co[2] + origin[2]; | |||||
| verts_array_buff[index_count * 3 + 2] = -vert.co[1] + -origin[1]; | |||||
| normal_buffer[index_count * 3 + 0] = vert.no[0]; | |||||
| normal_buffer[index_count * 3 + 1] = vert.no[2]; | |||||
| normal_buffer[index_count * 3 + 2] = (-vert.no[1]); | |||||
| if (UV) { | |||||
| const uv = UV[index].uv; | |||||
| uv_buffer[index_count * 2 + 0] = uv[0]; | |||||
| uv_buffer[index_count * 2 + 1] = uv[1]; | |||||
| } | |||||
| index_count++; | |||||
| } | |||||
| indexi += 2; | |||||
| } | |||||
| } | |||||
| geometry.setAttribute('position', new BufferAttribute(verts_array_buff, 3)); | |||||
| geometry.setIndex(new BufferAttribute(face_buffer, 1)); | |||||
| geometry.setAttribute('normal', new BufferAttribute(normal_buffer, 3)); | |||||
| geometry.setAttribute('uv', new BufferAttribute(uv_buffer, 2)); | |||||
| // geometry.blend_mat = materials[pick_material]; | |||||
| return geometry; | |||||
| } | |||||
| // function createThreeJSGeometry (blender_mesh, origin) { | |||||
| // // get materials | |||||
| // const mats = blender_mesh.mat, | |||||
| // materials = []; | |||||
| // for (var i = 0; i < mats.length; i++) { | |||||
| // const material = createThreeJSMaterial(mats[i]); | |||||
| // materials.push(material); | |||||
| // } | |||||
| // | |||||
| // let pick_material = 0, | |||||
| // mesh = blender_mesh, | |||||
| // faces = mesh.mpoly, | |||||
| // loops = mesh.mloop, | |||||
| // UV = mesh.mloopuv, | |||||
| // verts = mesh.mvert, | |||||
| // vert_array = [], | |||||
| // face_array = [], | |||||
| // uv_array = [], | |||||
| // normal_array = []; | |||||
| // | |||||
| // const geometry = new Geometry(); | |||||
| // | |||||
| // if (!faces) return geometry; | |||||
| // | |||||
| // | |||||
| // let index_count = 0; | |||||
| // | |||||
| // let verts_array_buff = new Float32Array(verts.length * 3); | |||||
| // | |||||
| // for (var i = 0; i < verts.length; i++) { | |||||
| // let vert = verts[i]; | |||||
| // vert_array.push(new Vector3(vert.co[0] + origin[0], vert.co[2] + origin[2], -vert.co[1] - origin[1])); | |||||
| // } | |||||
| // | |||||
| // for (var i = 0; i < faces.length; i++) { | |||||
| // const face = faces[i] || faces; | |||||
| // const len = face.totloop; | |||||
| // const start = face.loopstart; | |||||
| // let indexi = 1; | |||||
| // | |||||
| // pick_material = face.mat_nr; | |||||
| // | |||||
| // while (indexi < len) { | |||||
| // const face_normals = []; | |||||
| // const face_index_array = []; | |||||
| // const face_uvs = []; | |||||
| // | |||||
| // let index = 0; | |||||
| // | |||||
| // for (let l = 0; l < 3; l++) { | |||||
| // // Per Vertice | |||||
| // | |||||
| // if ((indexi - 1) + l < len) { | |||||
| // index = start + (indexi - 1) + l; | |||||
| // } else { | |||||
| // index = start; | |||||
| // } | |||||
| // | |||||
| // const v = loops[index].v; | |||||
| // var vert = verts[v]; | |||||
| // | |||||
| // face_index_array.push(v); | |||||
| // | |||||
| // index_count++; | |||||
| // | |||||
| // // get normals, which are 16byte ints, and norm them back into floats. | |||||
| // | |||||
| // const n1 = vert.no[0], | |||||
| // n2 = vert.no[2], | |||||
| // n3 = -vert.no[1]; | |||||
| // | |||||
| // const nl = 1; | |||||
| // | |||||
| // Math.sqrt((n1 * n1) + (n2 * n2) + (n3 * n3)); | |||||
| // | |||||
| // face_normals.push(new Vector3(n1 / nl, n2 / nl, n3 / nl)); | |||||
| // | |||||
| // if (UV) { | |||||
| // const uv = UV[index].uv; | |||||
| // face_uvs.push(new Vector2(uv[0], uv[1])); | |||||
| // } | |||||
| // } | |||||
| // uv_array.push(face_uvs); | |||||
| // face_array.push(new Face3( | |||||
| // face_index_array[0], face_index_array[1], face_index_array[2], | |||||
| // face_normals, | |||||
| // )); | |||||
| // | |||||
| // indexi += 2; | |||||
| // } | |||||
| // } | |||||
| // geometry.blend_mat = materials[pick_material]; | |||||
| // geometry.vertices = vert_array; | |||||
| // geometry.faces = face_array; | |||||
| // if (uv_array.length > 0) { | |||||
| // geometry.faceVertexUvs = [uv_array]; | |||||
| // } | |||||
| // | |||||
| // geometry.uvsNeedUpdate = true; | |||||
| // | |||||
| // // Well, using blender file normals does not work. Will need to investigate why normals from the blender file do not provide correct results. | |||||
| // // For now, have Three calculate normals. | |||||
| // | |||||
| // geometry.computeVertexNormals(); | |||||
| // | |||||
| // | |||||
| // return geometry; | |||||
| // } |
| /* eslint-disable camelcase, no-unused-vars, no-empty, no-constant-condition */ | |||||
| import {arrayBufferToBase64} from 'ts-browser-helpers'; | |||||
| import {Texture} from 'threepipe'; | |||||
| let blender_texture_cache = {}; | |||||
| export function createThreeJSTexture (image) { | |||||
| let parsed_blend_file = image.__blender_file__; | |||||
| let texture = null; | |||||
| let name = image.aname; | |||||
| if (image.packedfile) { | |||||
| if (blender_texture_cache[name]) { | |||||
| texture = blender_texture_cache[name]; | |||||
| } else { | |||||
| // get the extension | |||||
| let ext = name.split('.').pop(); | |||||
| let data = image.packedfile; | |||||
| let size = data.size; | |||||
| let offset = data.data.__data_address__; | |||||
| let raw_data = parsed_blend_file.byte.slice(offset, offset + size); | |||||
| let encodedData = arrayBufferToBase64(raw_data); | |||||
| let dataURI; | |||||
| switch (ext) { | |||||
| case 'png': | |||||
| dataURI = 'data:image/png;base64,' + encodedData; | |||||
| break; | |||||
| case 'jpg': | |||||
| dataURI = 'data:image/jpeg;base64,' + encodedData; | |||||
| break; | |||||
| default: | |||||
| console.warn('Unsupported image type', ext); | |||||
| } | |||||
| // eslint-disable-next-line no-undef | |||||
| let img = new Image(); | |||||
| img.src = dataURI; | |||||
| texture = new Texture(img); | |||||
| img.onload = () => { | |||||
| texture.needsUpdate = true; | |||||
| // todo colorspace? | |||||
| }; | |||||
| blender_texture_cache[name] = texture; | |||||
| } | |||||
| } | |||||
| return texture; | |||||
| } |
| import {BufferAttribute, BufferGeometry, Vector3Tuple} from 'threepipe' | |||||
| export function createBufferGeometry(mesh: any, origin: Vector3Tuple) { | |||||
| const | |||||
| faces = Array.isArray(mesh.mpoly) ? mesh.mpoly as any[] : [mesh.mpoly], | |||||
| loops = mesh.mloop, | |||||
| uv = mesh.mloopuv, | |||||
| vertices = mesh.mvert | |||||
| const geometry = new BufferGeometry() | |||||
| if (!faces) return geometry | |||||
| const size = faces.reduce((acc, face) => acc + Math.floor(face.totloop * 3.0 / 2), 0) | |||||
| const indices = new Uint32Array(size) | |||||
| const uvs = new Float32Array(size * 2) | |||||
| const normals = new Float32Array(size * 3) | |||||
| const positions = new Float32Array(size * 3) | |||||
| let currentIndex = 0 | |||||
| for (const face of faces) { | |||||
| const len = face.totloop | |||||
| const start = face.loopstart | |||||
| let indexi = 1 | |||||
| while (indexi < len) { | |||||
| let index = 0 | |||||
| for (let l = 0; l < 3; l++) { | |||||
| // Per Vertex | |||||
| index = start | |||||
| if (indexi - 1 + l < len) | |||||
| index += indexi - 1 + l | |||||
| const loop = loops[index] | |||||
| const vert = vertices[loop.v] | |||||
| indices[currentIndex] = currentIndex | |||||
| positions[currentIndex * 3 + 0] = vert.co[0] + origin[0] | |||||
| positions[currentIndex * 3 + 1] = vert.co[2] + origin[2] | |||||
| positions[currentIndex * 3 + 2] = -vert.co[1] + -origin[1] | |||||
| normals[currentIndex * 3 + 0] = vert.no[0] | |||||
| normals[currentIndex * 3 + 1] = vert.no[2] | |||||
| normals[currentIndex * 3 + 2] = -vert.no[1] | |||||
| if (uv) { | |||||
| uvs[currentIndex * 2 + 0] = uv[index].uv[0] | |||||
| uvs[currentIndex * 2 + 1] = uv[index].uv[1] | |||||
| } | |||||
| currentIndex++ | |||||
| } | |||||
| indexi += 2 | |||||
| } | |||||
| } | |||||
| geometry.setAttribute('position', new BufferAttribute(positions, 3)) | |||||
| geometry.setIndex(new BufferAttribute(indices, 1)) | |||||
| geometry.setAttribute('normal', new BufferAttribute(normals, 3)) | |||||
| geometry.setAttribute('uv', new BufferAttribute(uvs, 2)) | |||||
| return geometry | |||||
| } |
| import {createLight} from './light' | |||||
| import {createMesh} from './mesh' | |||||
| import {Object3D} from 'three' | |||||
| const blenderObjectTypes = { | |||||
| mesh: 1, | |||||
| lamp: 10, | |||||
| } | |||||
| export async function createObjects(file: any) { | |||||
| const objects: (Object3D|null)[] = [] | |||||
| const blendObjects = file.objects.Object | |||||
| for (const object of blendObjects) { | |||||
| switch (object.type) { | |||||
| case blenderObjectTypes.mesh: | |||||
| objects.push(createMesh(object)) | |||||
| break | |||||
| case blenderObjectTypes.lamp: | |||||
| objects.push(createLight(object)) | |||||
| break | |||||
| default: | |||||
| console.warn('Unsupported object type', object.type) | |||||
| } | |||||
| } | |||||
| return objects.filter(o => !!o) as Object3D[] | |||||
| } |
| import {PointLight} from 'threepipe' | |||||
| const blenderLightTypes = { | |||||
| point: 0, | |||||
| sun: 1, | |||||
| spot: 0, | |||||
| hemi: 0, | |||||
| area: 0, | |||||
| } | |||||
| export function createLight(lamp: any) { | |||||
| const ldata = lamp.data | |||||
| const position = [lamp.loc[0], lamp.loc[2], -lamp.loc[1]] | |||||
| const color = ldata.r * 255 << 16 | ldata.g * 255 << 8 | ldata.b * 255 << 0 | |||||
| const intensity = ldata.energy | |||||
| const distance = 0 | |||||
| let light = null | |||||
| switch (ldata.type) { | |||||
| case blenderLightTypes.point: | |||||
| light = new PointLight(color, intensity, distance) | |||||
| light.position.fromArray(position, 0) | |||||
| light.castShadow = true | |||||
| break | |||||
| case blenderLightTypes.sun: | |||||
| light = new PointLight(color, intensity, distance) | |||||
| light.position.fromArray(position, 0) | |||||
| light.castShadow = true | |||||
| light.shadow.mapSize.width = 1024 | |||||
| light.shadow.mapSize.height = 1024 | |||||
| light.shadow.camera.near = 0.01 | |||||
| light.shadow.camera.far = 500 | |||||
| break | |||||
| default: | |||||
| console.warn('Unsupported light type', ldata.type) | |||||
| } | |||||
| return light | |||||
| } |
| import {MeshPhysicalMaterial} from 'threepipe' | |||||
| // todo see blender gltf exporter and convert to js. structure is the same | |||||
| export function createMaterial(mat: any) { | |||||
| const material = new MeshPhysicalMaterial() | |||||
| material.color.setRGB(mat.r, mat.g, mat.b) | |||||
| material.roughness = mat.roughness !== undefined ? mat.roughness : 0.4 | |||||
| material.metalness = mat.metallic !== undefined ? mat.metallic : 0.0 | |||||
| material.opacity = mat.alpha !== undefined ? mat.alpha : 0.0 | |||||
| material.transparent = material.opacity < 1.0 | |||||
| return material | |||||
| } |
| import {Mesh} from 'threepipe' | |||||
| import {createBufferGeometry} from './geometry' | |||||
| import {createMaterial} from './material' | |||||
| export function createMesh(object: any) { | |||||
| if (!object.data) return null | |||||
| const geometry = createBufferGeometry(object.data, [0, 0, 0]) | |||||
| const mat = object.data.mat[0] | |||||
| const material = mat ? createMaterial(mat) : undefined | |||||
| const mesh = new Mesh(geometry, material) | |||||
| mesh.castShadow = true | |||||
| mesh.receiveShadow = true | |||||
| mesh.rotateZ(object.rot[2]) | |||||
| mesh.rotateY(object.rot[1]) | |||||
| mesh.rotateX(object.rot[0]) | |||||
| mesh.scale.fromArray(object.size, 0) | |||||
| mesh.position.fromArray([object.loc[0], object.loc[2], -object.loc[1]], 0) | |||||
| return mesh | |||||
| } |
| { | |||||
| "compilerOptions": { | |||||
| "baseUrl": "./src", | |||||
| "rootDir": "./src", | |||||
| "allowJs": false, | |||||
| "checkJs": false, | |||||
| "skipLibCheck": true, | |||||
| "allowSyntheticDefaultImports": true, | |||||
| "experimentalDecorators": true, | |||||
| "isolatedModules": true, | |||||
| "module": "es2020", | |||||
| "noImplicitAny": true, | |||||
| "declaration": true, | |||||
| "declarationMap": true, | |||||
| "declarationDir": "dist", | |||||
| "outDir": "dist", | |||||
| "noImplicitThis": true, | |||||
| "noUnusedLocals": true, | |||||
| "noUnusedParameters": true, | |||||
| "removeComments": false, | |||||
| "preserveConstEnums": true, | |||||
| "moduleResolution": "node", | |||||
| "emitDecoratorMetadata": false, | |||||
| "sourceMap": true, | |||||
| "target": "ES2020", | |||||
| "strictNullChecks": true, | |||||
| "lib": [ | |||||
| "es2020", | |||||
| "esnext", | |||||
| "dom" | |||||
| ] | |||||
| }, | |||||
| "include": [ | |||||
| "src/**/*" | |||||
| ], | |||||
| "exclude": [ | |||||
| "node_modules", | |||||
| "**/*.spec.ts", | |||||
| "dist" | |||||
| ] | |||||
| } |
| { | |||||
| "extends": [ | |||||
| "../../typedoc.json" | |||||
| ], | |||||
| "entryPoints": [ | |||||
| "src/index.ts" | |||||
| ], | |||||
| "name": "Threepipe Blend File Importer", | |||||
| "readme": "none" | |||||
| } |
| export {Box2} from 'three' | export {Box2} from 'three' | ||||
| export {Line3} from 'three' | export {Line3} from 'three' | ||||
| export {Euler} from 'three' | export {Euler} from 'three' | ||||
| export {Vector4} from 'three' | |||||
| export {Vector3} from 'three' | |||||
| export {Vector2} from 'three' | |||||
| export {Vector4, type Vector4Tuple} from 'three' | |||||
| export {Vector3, type Vector3Tuple} from 'three' | |||||
| export {Vector2, type Vector2Tuple} from 'three' | |||||
| export {Quaternion} from 'three' | export {Quaternion} from 'three' | ||||
| export {Color} from 'three' | export {Color} from 'three' | ||||
| export {ColorManagement} from 'three' | export {ColorManagement} from 'three' |
| export const VERSION = '0.0.13' | |||||
| export const VERSION = '0.0.14' |