Просмотр исходного кода

Add wip @threepipe/plugin-blend-importer for parsing and importing .blend file and an example.

master
Palash Bansal 2 лет назад
Родитель
Сommit
e6e2a397a9
Аккаунт пользователя с таким Email не найден
31 измененных файлов: 1999 добавлений и 12 удалений
  1. 38
    1
      README.md
  2. 54
    0
      examples/blend-load/index.html
  3. 1
    0
      examples/index.html
  4. 2
    1
      examples/tweakpane-editor/index.html
  5. 8
    3
      examples/tweakpane-editor/script.ts
  6. 2
    2
      package-lock.json
  7. 1
    1
      package.json
  8. 30
    0
      plugins/blend-importer/package-lock.json
  9. 57
    0
      plugins/blend-importer/package.json
  10. 99
    0
      plugins/blend-importer/rollup.config.mjs
  11. 33
    0
      plugins/blend-importer/src/BlendLoadPlugin.ts
  12. 36
    0
      plugins/blend-importer/src/global.d.ts
  13. 1
    0
      plugins/blend-importer/src/index.ts
  14. 21
    0
      plugins/blend-importer/src/js-blend/license.md
  15. 1
    0
      plugins/blend-importer/src/js-blend/main.d.ts
  16. 21
    0
      plugins/blend-importer/src/js-blend/main.js
  17. 783
    0
      plugins/blend-importer/src/js-blend/parser/parser.js
  18. 91
    0
      plugins/blend-importer/src/js-blend/threejs/blend_three.js
  19. 47
    0
      plugins/blend-importer/src/js-blend/threejs/light.js
  20. 175
    0
      plugins/blend-importer/src/js-blend/threejs/material.js
  21. 209
    0
      plugins/blend-importer/src/js-blend/threejs/mesh.js
  22. 60
    0
      plugins/blend-importer/src/js-blend/threejs/texture.js
  23. 68
    0
      plugins/blend-importer/src/loader/geometry.ts
  24. 26
    0
      plugins/blend-importer/src/loader/index.ts
  25. 42
    0
      plugins/blend-importer/src/loader/light.ts
  26. 12
    0
      plugins/blend-importer/src/loader/material.ts
  27. 26
    0
      plugins/blend-importer/src/loader/mesh.ts
  28. 41
    0
      plugins/blend-importer/tsconfig.json
  29. 10
    0
      plugins/blend-importer/typedoc.json
  30. 3
    3
      src/three/Threejs.ts
  31. 1
    1
      src/viewer/version.ts

+ 38
- 1
README.md Просмотреть файл

@@ -106,7 +106,8 @@ To make changes and run the example, click on the CodePen button on the top righ
- [Packages](#threepipe-packages)
- [@threepipe/plugin-tweakpane](#threepipeplugin-tweakpane) Tweakpane UI 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

@@ -2626,3 +2627,39 @@ const model1 = await viewer.load<IObject3D>('data:model/3mf;base64,...')
```

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.)


+ 54
- 0
examples/blend-load/index.html Просмотреть файл

@@ -0,0 +1,54 @@
<!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>

+ 1
- 0
examples/index.html Просмотреть файл

@@ -249,6 +249,7 @@
<li><a href="./stl-load/">STL Load </a></li>
<li><a href="./ktx2-load/">KTX2 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>
</ul>
<h2 class="category">Export</h2>

+ 2
- 1
examples/tweakpane-editor/index.html Просмотреть файл

@@ -19,7 +19,8 @@
"threepipe": "./../../dist/index.mjs",
"@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/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"
}
}


+ 8
- 3
examples/tweakpane-editor/script.ts Просмотреть файл

@@ -11,6 +11,7 @@ import {
KTX2LoadPlugin,
KTXLoadPlugin,
NormalBufferPlugin,
PickingPlugin,
PLYLoadPlugin,
ProgressivePlugin,
RenderTargetPreviewPlugin,
@@ -24,6 +25,7 @@ import {
} from 'threepipe'
import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane'
import {TweakpaneEditorPlugin} from '@threepipe/plugin-tweakpane-editor'
import {BlendLoadPlugin} from '@threepipe/plugin-blend-importer'
import {extraImportPlugins} from '@threepipe/plugin-extra-importers'

async function init() {
@@ -46,9 +48,10 @@ async function init() {

await viewer.addPlugins([
new ProgressivePlugin(),
new GLTFAnimationPlugin(),
new CameraViewPlugin(),
new ViewerUiConfigPlugin(),
GLTFAnimationPlugin,
PickingPlugin,
CameraViewPlugin,
ViewerUiConfigPlugin,
// new SceneUiConfigPlugin(), // this is already in ViewerUiPlugin
new DepthBufferPlugin(HalfFloatType, true, true),
new NormalBufferPlugin(HalfFloatType, false),
@@ -60,6 +63,7 @@ async function init() {
Rhino3dmLoadPlugin,
STLLoadPlugin,
USDZLoadPlugin,
BlendLoadPlugin,
...extraImportPlugins,
])

@@ -69,6 +73,7 @@ async function init() {

editor.loadPlugins({
['Viewer']: [ViewerUiConfigPlugin, SceneUiConfigPlugin, DropzonePlugin, FullScreenPlugin],
['Interaction']: [PickingPlugin],
['GBuffer']: [DepthBufferPlugin, NormalBufferPlugin],
['Post-processing']: [TonemapPlugin, ProgressivePlugin, FrameFadePlugin],
['Animation']: [GLTFAnimationPlugin, CameraViewPlugin],

+ 2
- 2
package-lock.json Просмотреть файл

@@ -1,12 +1,12 @@
{
"name": "threepipe",
"version": "0.0.13",
"version": "0.0.14",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "threepipe",
"version": "0.0.13",
"version": "0.0.14",
"license": "Apache-2.0",
"dependencies": {
"@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1016/package.tgz",

+ 1
- 1
package.json Просмотреть файл

@@ -1,6 +1,6 @@
{
"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.",
"main": "src/index.ts",
"module": "dist/index.mjs",

+ 30
- 0
plugins/blend-importer/package-lock.json Просмотреть файл

@@ -0,0 +1,30 @@
{
"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"
}
}
}

+ 57
- 0
plugins/blend-importer/package.json Просмотреть файл

@@ -0,0 +1,57 @@
{
"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"
}
}

+ 99
- 0
plugins/blend-importer/rollup.config.mjs Просмотреть файл

@@ -0,0 +1,99 @@
// 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.
},
})
]
}

+ 33
- 0
plugins/blend-importer/src/BlendLoadPlugin.ts Просмотреть файл

@@ -0,0 +1,33 @@
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)
}

+ 36
- 0
plugins/blend-importer/src/global.d.ts Просмотреть файл

@@ -0,0 +1,36 @@
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

+ 1
- 0
plugins/blend-importer/src/index.ts Просмотреть файл

@@ -0,0 +1 @@
export {BlendLoadPlugin} from './BlendLoadPlugin'

+ 21
- 0
plugins/blend-importer/src/js-blend/license.md Просмотреть файл

@@ -0,0 +1,21 @@
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.

+ 1
- 0
plugins/blend-importer/src/js-blend/main.d.ts Просмотреть файл

@@ -0,0 +1 @@
export function parseBlend(arrayBuffer: ArrayBuffer): Promise<any>

+ 21
- 0
plugins/blend-importer/src/js-blend/main.js Просмотреть файл

@@ -0,0 +1,21 @@
/* 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)
})
}

+ 783
- 0
plugins/blend-importer/src/js-blend/parser/parser.js Просмотреть файл

@@ -0,0 +1,783 @@
/* 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;

+ 91
- 0
plugins/blend-importer/src/js-blend/threejs/blend_three.js Просмотреть файл

@@ -0,0 +1,91 @@
/* 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),
};
}

+ 47
- 0
plugins/blend-importer/src/js-blend/threejs/light.js Просмотреть файл

@@ -0,0 +1,47 @@
/* 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;
}

+ 175
- 0
plugins/blend-importer/src/js-blend/threejs/material.js Просмотреть файл

@@ -0,0 +1,175 @@
/* 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;
}


+ 209
- 0
plugins/blend-importer/src/js-blend/threejs/mesh.js Просмотреть файл

@@ -0,0 +1,209 @@
/* 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;
// }

+ 60
- 0
plugins/blend-importer/src/js-blend/threejs/texture.js Просмотреть файл

@@ -0,0 +1,60 @@
/* 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;
}

+ 68
- 0
plugins/blend-importer/src/loader/geometry.ts Просмотреть файл

@@ -0,0 +1,68 @@
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
}

+ 26
- 0
plugins/blend-importer/src/loader/index.ts Просмотреть файл

@@ -0,0 +1,26 @@
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[]
}

+ 42
- 0
plugins/blend-importer/src/loader/light.ts Просмотреть файл

@@ -0,0 +1,42 @@
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
}

+ 12
- 0
plugins/blend-importer/src/loader/material.ts Просмотреть файл

@@ -0,0 +1,12 @@
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
}

+ 26
- 0
plugins/blend-importer/src/loader/mesh.ts Просмотреть файл

@@ -0,0 +1,26 @@
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
}

+ 41
- 0
plugins/blend-importer/tsconfig.json Просмотреть файл

@@ -0,0 +1,41 @@
{
"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"
]
}

+ 10
- 0
plugins/blend-importer/typedoc.json Просмотреть файл

@@ -0,0 +1,10 @@
{
"extends": [
"../../typedoc.json"
],
"entryPoints": [
"src/index.ts"
],
"name": "Threepipe Blend File Importer",
"readme": "none"
}

+ 3
- 3
src/three/Threejs.ts Просмотреть файл

@@ -173,9 +173,9 @@ export {Box3} from 'three'
export {Box2} from 'three'
export {Line3} 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 {Color} from 'three'
export {ColorManagement} from 'three'

+ 1
- 1
src/viewer/version.ts Просмотреть файл

@@ -1 +1 @@
export const VERSION = '0.0.13'
export const VERSION = '0.0.14'

Загрузка…
Отмена
Сохранить