UNPKG

@threepipe/plugin-blend-importer

Version:
1,039 lines (1,038 loc) 37.2 kB
/** * @license * @threepipe/plugin-blend-importer v0.0.8 * Copyright 2022-2025 repalash <palash@shaders.app> * Apache-2.0 License * See ./dependencies.txt for any bundled third-party dependencies and licenses. */ import { Quaternion, Euler, Vector3, BaseImporterPlugin, Importer, FileLoader, Object3D, Object3D2, PerspectiveCamera0, OrthographicCamera0, PointLight2, PhysicalMaterial, Mesh2, BufferGeometry2, BufferAttribute } from "threepipe"; let worker = null; const return_object = { loadBlendFromArrayBuffer: function(array_buffer) { return_object.ready = false; { 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() { let data = null, _data = null, BIG_ENDIAN = false, pointer_size = 0, struct_names = [], offset = 0, current_SDNA_template = null, templates = {}, FILE = null, ERROR = null, AB = null; const self = this; function parseFile(msg) { if (typeof msg.data == "object") { AB = null; data = null; BIG_ENDIAN = false; pointer_size = 0; struct_names = []; offset = 0; current_SDNA_template = null; _data = msg.data; AB = _data.slice(); data = new DataView(_data); FILE = new BLENDER_FILE(AB); readFile(); self.postMessage(FILE, ERROR); } } const BLENDER_FILE = function(AB2) { this.AB = AB2; this.byte = new Uint8Array(AB2); this.dv = new DataView(AB2); 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(offset2) { const pointerLow = this.dv.getUint32(offset2, this.template.endianess); if (this.template.pointer_size > 4) { const pointerHigh = this.dv.getUint32(offset2 + 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; function float64Prop(offset2, Blender_Array_Length, length) { return { get: function() { return Blender_Array_Length > 1 ? new Float64Array(this.__blender_file__.AB, this.__data_address__ + offset2, length) : this.__blender_file__.dv.getFloat64(this.__data_address__ + offset2, this.__blender_file__.template.endianess); }, set: function(float) { if (Blender_Array_Length > 1) ; else { this.__blender_file__.dv.setFloat64(this.__data_address__ + offset2, float, this.__blender_file__.template.endianess); } }, enumerable: true, configurable: true }; } function floatProp(offset2, Blender_Array_Length, length) { return { get: function() { return Blender_Array_Length > 1 ? new Float32Array(this.__blender_file__.AB, this.__data_address__ + offset2, length) : this.__blender_file__.dv.getFloat32(this.__data_address__ + offset2, this.__blender_file__.template.endianess); }, set: function(float) { if (Blender_Array_Length > 1) ; else { this.__blender_file__.dv.setFloat32(this.__data_address__ + offset2, float, this.__blender_file__.template.endianess); } }, enumerable: true, configurable: true }; } function intProp(offset2, Blender_Array_Length, length) { return { get: function() { return Blender_Array_Length > 1 ? new Int32Array(this.__blender_file__.AB, this.__data_address__ + offset2, length) : this.__blender_file__.dv.getInt32(this.__data_address__ + offset2, this.__blender_file__.template.endianess); }, set: function(float) { if (Blender_Array_Length > 1) ; else { this.__blender_file__.dv.setInt32(this.__data_address__ + offset2, float, this.__blender_file__.template.endianess); } }, enumerable: true, configurable: true }; } function shortProp(offset2, Blender_Array_Length, length) { return { get: function() { return Blender_Array_Length > 1 ? new Int16Array(this.__blender_file__.AB, this.__data_address__ + offset2, length) : this.__blender_file__.dv.getInt16(this.__data_address__ + offset2, this.__blender_file__.template.endianess); }, set: function(float) { if (Blender_Array_Length > 1) ; else { this.__blender_file__.dv.setInt16(this.__data_address__ + offset2, float, this.__blender_file__.template.endianess); } }, enumerable: true, configurable: true }; } const uShortProp = (offset2, Blender_Array_Length, length) => { return { get: function() { return Blender_Array_Length > 1 ? new Uint16Array(this.__blender_file__.AB, this.__data_address__ + offset2, length) : this.__blender_file__.dv.getUint16(this.__data_address__ + offset2, this.__blender_file__.template.endianess); }, set: function(float) { if (Blender_Array_Length > 1) ; else { this.__blender_file__.dv.setUint16(this.__data_address__ + offset2, float, this.__blender_file__.template.endianess); } }, enumerable: true, configurable: true }; }; function charProp(offset2, Blender_Array_Length, length) { return { get: function() { if (Blender_Array_Length > 1) { let start = this.__data_address__ + offset2; 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__ + offset2]; }, 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__ + offset2 + i] = string.charCodeAt(i) | 0; } else { this.__blender_file__.byte[this.__data_address__ + offset2 + i] = 0; } i++; } } else { this.__blender_file__.byte[this.__data_address__ + offset2] = byte | 0; } }, enumerable: true, configurable: true }; } function pointerProp2(offset2) { return { get: function() { let pointer = this.__blender_file__.getPointer(this.__data_address__ + offset2, 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() { }, enumerable: true, configurable: true }; } function pointerProp(offset2, Blender_Array_Length, length) { return { get: function() { if (Blender_Array_Length > 1) { let array = []; let j = 0; let off = offset2; 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__ + offset2, this.__blender_file__); return this.__blender_file__.memory_lookup[pointer]; } }, set: function() { }, enumerable: true, configurable: true }; } function compileProp(obj, name, type, offset2, array_size, IS_POINTER, pointer_size2, length) { if (!IS_POINTER) { switch (type) { case "double": Object.defineProperty(obj, name, float64Prop(offset2, array_size, length >> 3)); break; case "float": Object.defineProperty(obj, name, floatProp(offset2, array_size, length >> 2)); break; case "int": Object.defineProperty(obj, name, intProp(offset2, array_size, length >> 2)); break; case "short": Object.defineProperty(obj, name, shortProp(offset2, array_size, length >> 1)); break; case "ushort": Object.defineProperty(obj, name, uShortProp(offset2, array_size, length >> 1)); break; case "char": case "uchar": Object.defineProperty(obj, name, charProp(offset2, array_size, length)); break; default: obj[name] = {}; obj.__list__.push(name, type, length, offset2, array_size, IS_POINTER); } obj._length += length; offset2 += length; } else { Object.defineProperty(obj, name, pointerProp(offset2, array_size, pointer_size2)); obj._length += pointer_size2 * array_size; offset2 += pointer_size2 * array_size; } return offset2; } 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 jsName = name === "void" ? "void_" : name; const blen_struct = Function("function " + jsName + "(){}; return " + jsName)(); blen_struct.prototype = new BLENDER_STRUCTURE(); blen_struct.prototype.blender_name = name; blen_struct.prototype.__pointers = []; blen_struct.prototype.__list__ = []; const DNA = this.SDNA[name] = { constructor: blen_struct }; let offset2 = 0; 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; if (match = _name.match(/(\*?)(\*?)(\w+)(\[(\w*)\])?(\[(\w*)\])?/)) { _name = match[3]; if (match[1]) { Pointer_Match = 10; blen_struct.prototype.__pointers.push(_name); } if (match[2]) { PointerToArray = true; } 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, length, isArray: Blender_Array_Length > 0 }; if (PointerToArray) { Object.defineProperty(blen_struct.prototype, _name, pointerProp2(offset2)); offset2 += pointer_size; } else if (Suparray_match > 1) { const array_names = new Array(Suparray_match); for (let j = 0; j < Suparray_match; j++) { let array_name_ = `__${_name}[${j}]__`; array_names[j] = array_name_; offset2 = compileProp(blen_struct.prototype, array_name_, type, offset2, Blender_Array_Length, Pointer_Match, pointer_size, array_length); } Object.defineProperty(blen_struct.prototype, _name, { get: /* @__PURE__ */ function(array_names2) { return function() { const array = []; for (let i2 = 0; i2 < array_names2.length; i2++) { array.push(this[array_names2[i2]]); } return array; }; }(array_names), enumerable: true, configurable: true }); } else { offset2 = compileProp(blen_struct.prototype, _name, type, offset2, 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; } } }; const 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; }; BLENDER_STRUCTURE.prototype = { setData: function(pointer, _data_offset, data_block_length, BLENDER_FILE2) { if (this.__list__ === null) return this; BLENDER_FILE2.addObject(this); this.__blender_file__ = BLENDER_FILE2; const struct = this.__list__; let j = 0, i = 0, name = "", type, length, Blender_Array_Length, offset2, constructor; this.__data_address__ = _data_offset; if (struct === null) return this; for (i = 0; i < struct.length; i += 6) { name = struct[i]; type = struct[i + 1]; Blender_Array_Length = struct[i + 4]; struct[i + 5]; offset2 = 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, offset2, offset2 + length / Blender_Array_Length, BLENDER_FILE2)); } else this[name].push(null); offset2 += length / Blender_Array_Length; j++; } } else { if (current_SDNA_template.getSDNAStructureConstructor(type)) { constructor = current_SDNA_template.getSDNAStructureConstructor(type); this[name] = new constructor().setData(0, offset2, length + offset2, BLENDER_FILE2); } else this[name] = null; } } this.__list__ = null; return this; }, get aname() { if (this.id) return this.id.name.slice(2); else return void 0; } }; function toString(buffer, _in, _out) { return String.fromCharCode.apply(String, new Uint8Array(buffer, _in, _out - _in)); } function seekCheck(buffer, _in, str) { const length = str.length; for (let i = 0; i < length; i++) { if (buffer.getUint8(_in + i) !== str.charCodeAt(i)) { return false; } } return true; } function readFile() { let count = 0; let offset2 = 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; const magic = toString(_data, offset, 7); if (magic !== "BLENDER") return ERROR = "File supplied is not a .blend compatible Blender file."; offset += 7; pointer_size = toString(_data, offset++, offset) == "_" ? 4 : 8; BIG_ENDIAN = toString(_data, offset++, offset) !== "V"; const version = toString(_data, offset, offset + 3); if (!templates[version]) { templates[version] = new MASTER_SDNA_SCHEMA(version); } current_SDNA_template = templates[version]; FILE.template = current_SDNA_template; offset += 3; if (!current_SDNA_template.SDNA_SET) { current_SDNA_template.endianess = BIG_ENDIAN; current_SDNA_template.pointer_size = pointer_size; offset2 = offset; while (true) { sdna_index = data.getInt32(offset2 + pointer_size + 8, BIG_ENDIAN); code = toString(_data, offset2, offset2 + 4).replace(/\u0000/g, ""); block_length = data.getInt32(offset2 + 4, true); offset2 += 16 + pointer_size; if (code === "DNA1") { count = 0; let types = [], names = [], lengths = [], curr_name = ""; offset2 += 8; count = data.getInt32(offset2, true); offset2 += 4; curr_count = 0; 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++; } for (let j = 0; j < 8; j++) { if (seekCheck(data, offset2, "TYPE")) break; offset2++; } if (!seekCheck(data, offset2, "TYPE")) { ERROR = "Unexpected alignment error in SDNA parsing"; break; } offset2 += 4; count = data.getUint32(offset2, true); offset2 += 4; curr_count = 0; 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++; } for (let j = 0; j < 8; j++) { if (seekCheck(data, offset2, "TLEN")) break; offset2++; } if (!seekCheck(data, offset2, "TLEN")) { ERROR = "Unexpected alignment error in SDNA parsing"; break; } offset2 += 4; curr_count = 0; while (curr_count < count) { lengths.push(data.getInt16(offset2, BIG_ENDIAN)); offset2 += 2; curr_count++; } for (let j = 0; j < 8; j++) { if (seekCheck(data, offset2, "STRC")) break; offset2++; } if (!seekCheck(data, offset2, "STRC")) { ERROR = "Unexpected alignment error in SDNA parsing"; break; } offset2 += 4; const structure_count = data.getInt32(offset2, BIG_ENDIAN); offset2 += 4; curr_count = 0; 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); 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++; } 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; } } while (true) { for (let j = 0; j < 8; j++) { if (data.getInt8(offset) === 0) { offset++; continue; } break; } if (data.getInt8(offset) === 0) { debugger; } data_offset = offset; if (offset + pointer_size + 12 >= data.byteLength) { ERROR = "Unexpected end of file while parsing"; break; } sdna_index = data.getInt32(offset + pointer_size + 8, BIG_ENDIAN); const code_str = toString(_data, offset, offset + 4); offset2 = offset + 16 + pointer_size; const blockLength = data.getInt32(offset + 4, true); if (blockLength < 0 || offset + blockLength + 16 + pointer_size > data.byteLength) { ERROR = "Invalid block length detected"; break; } offset += blockLength + 16 + pointer_size; if (code_str === "DNA1") ; else if (code_str === "ENDB") break; else if (code_str === "TEST") { const data_start = data_offset + pointer_size + 16; const width = data.getInt32(data_start, BIG_ENDIAN); const height = data.getInt32(data_start + 4, BIG_ENDIAN); if (width * height > 0) { const data_len = width * height * 4; if (blockLength < data_len + 8) { ERROR = "Invalid TEST block length detected"; break; } const image_data = new Uint32Array(_data, data_start + 8, data_len >> 2); const image = { width, height, data: image_data }; FILE.thumbnail = image; } } else { const data_start = data_offset + pointer_size + 16; 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 && constructor) { 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, err) { return_object.onParseReady(message, err); }; /** * 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 */ async function parseBlend(buffer, name = "") { return new Promise((res, rej) => { return_object.onParseReady = (file, error) => { if (error) console.error(error); res(file); }; return_object.loadBlendFromArrayBuffer(buffer, name); }); } function createCamera(object, ctx) { const cdata = object.data; if (!cdata) return void 0; const type = cdata.type; let camera; if (type === 0) { const sensorWidth = cdata.sensor_x || 36; const sensorHeight = cdata.sensor_y || 24; const lens = cdata.lens || 50; const fov = 2 * Math.atan(sensorHeight / (2 * lens)) * (180 / Math.PI); camera = new ctx.PerspectiveCamera( fov, sensorWidth / sensorHeight, cdata.clipsta || 0.1, cdata.clipend || 1e3 ); if ("shiftx" in cdata) camera.userData.shiftX = cdata.shiftx; if ("shiftx" in cdata) camera.userData.shiftX = cdata.shiftx; if ("shifty" in cdata) camera.userData.shiftY = cdata.shifty; } else if (type === 1) { const scale = cdata.ortho_scale || 1; const aspect = (cdata.sensor_x || 36) / (cdata.sensor_y || 24); const left = -scale * aspect / 2; const right = scale * aspect / 2; const top = scale / 2; const bottom = -scale / 2; camera = new ctx.OrthographicCamera( left, right, top, bottom, cdata.clipsta || 0.1, cdata.clipend || 1e3 ); if ("shiftx" in cdata) camera.userData.shiftX = cdata.shiftx; if ("shifty" in cdata) camera.userData.shiftY = cdata.shifty; } else { camera = new ctx.PerspectiveCamera(); console.warn("Unsupported camera type", type); } return camera; } const blenderLightTypes = { point: 0, sun: 1 }; function createLight(lamp, ctx) { 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 = void 0; switch (ldata.type) { case blenderLightTypes.point: light = new ctx.PointLight(color, intensity, distance); light.position.fromArray(position, 0); light.castShadow = true; break; case blenderLightTypes.sun: light = new ctx.PointLight(color, intensity, distance); light.position.fromArray(position, 0); light.castShadow = true; if (light.shadow) { 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; } function getLayer(layers, i) { if (!Array.isArray(layers)) return layers; return layers[i]; } function createBufferGeometry(meshData, ctx) { if (meshData.mpoly) return createBufferGeometryOld(meshData, ctx); const geometry = new ctx.BufferGeometry(); geometry.name = meshData.aname || ""; let vertices; let verticesData; let indices; let indicesData; if (meshData.vdata && meshData.vdata.layers && meshData.vdata.totlayer > 0) { for (let i = 0; i < meshData.vdata.totlayer; i++) { const layer = getLayer(meshData.vdata.layers, i); const data = layer.data || []; if (data.length === meshData.totvert) { if (vertices && (layer.name !== "position" || vertices.name === "position")) { continue; } vertices = layer; verticesData = data; } } } if (meshData.ldata && meshData.ldata.layers) { for (let i = 0; i < meshData.ldata.totlayer; i++) { const layer = getLayer(meshData.ldata.layers, i); const data = layer.data || []; if (data.length === meshData.totloop) { if (indices && (layer.name !== ".corner_vert" || indices.name === ".corner_vert")) { continue; } indices = layer; indicesData = data; } } } let faceIndices = []; if (meshData.poly_offset_indices && meshData.totpoly > 0) { faceIndices = [...new Int32Array(meshData.poly_offset_indices.__blender_file__.AB, meshData.poly_offset_indices.__data_address__, meshData.totpoly + 1)]; } if (verticesData && verticesData.length > 0) { const positions = new Float32Array(verticesData.length * 3); for (let j = 0; j < verticesData.length; j++) { const { x, y, z, co } = verticesData[j] || {}; if (x !== void 0) { positions[j * 3] = x; positions[j * 3 + 1] = z; positions[j * 3 + 2] = -y; } else if (co !== void 0) { positions[j * 3] = co[0]; positions[j * 3 + 1] = co[2]; positions[j * 3 + 2] = -co[1]; } else { console.error("BlendLoader - unknown vertex", verticesData[j]); break; } } geometry.setAttribute("position", new ctx.BufferAttribute(positions, 3)); } if (indicesData && indicesData.length > 0 && (verticesData == null ? void 0 : verticesData.length)) { const faceSize = meshData.totloop / meshData.totpoly; if (faceIndices.length > 0) { let totalTriangles = 0; for (let i = 0; i < faceIndices.length - 1; i++) { const faceVertCount = faceIndices[i + 1] - faceIndices[i]; totalTriangles += Math.max(0, faceVertCount - 2); } const indexes = new Uint32Array(totalTriangles * 3); let t = 0; for (let i = 0; i < faceIndices.length - 1; i++) { const faceStart = faceIndices[i]; const faceEnd = faceIndices[i + 1]; const faceVertCount = faceEnd - faceStart; if (faceVertCount >= 3) { const firstVert = indicesData[faceStart].i; for (let k = 1; k < faceVertCount - 1; k++) { indexes[t++] = firstVert; indexes[t++] = indicesData[faceStart + k].i; indexes[t++] = indicesData[faceStart + k + 1].i; } } } geometry.setIndex(new ctx.BufferAttribute(indexes, 1)); } else if (faceSize === 3 || faceSize === 4) { const isQuad = faceSize === 4; const faceCount = indices.length / faceSize; const indexes = new Uint32Array(faceCount * 3 * (isQuad ? 2 : 1)); if (faceSize !== 3 && faceSize !== 4) return geometry; for (let j = 0, t = 0; j < indices.length; j += faceSize) { const a = indices[j].i; const b = indices[j + 1].i; const c = indices[j + 2].i; indexes[t++] = a; indexes[t++] = b; indexes[t++] = c; if (isQuad) { const d = indices[j + 3].i; indexes[t++] = a; indexes[t++] = c; indexes[t++] = d; } } geometry.setIndex(new ctx.BufferAttribute(indexes, 1)); } } else if (faceIndices) { console.error("BlendLoader - no indices data found, but face indices are present", faceIndices); } if (geometry.attributes.position && !geometry.attributes.normal) geometry.computeVertexNormals(); return geometry; } function createBufferGeometryOld(mesh, ctx) { const faces = Array.isArray(mesh.mpoly) ? mesh.mpoly : [mesh.mpoly], loops = mesh.mloop, uv = mesh.mloopuv, vertices = mesh.mvert; const geometry = new ctx.BufferGeometry(); if (!faces) return geometry; const size = faces.reduce((acc, face) => acc + Math.floor(face.totloop * 3 / 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; let computeNormals = false; 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++) { index = start; if (indexi - 1 + l < len) index += indexi - 1 + l; const loop = loops[index]; const { co, no } = vertices[loop.v] || {}; indices[currentIndex] = currentIndex; if (co) { positions[currentIndex * 3 + 0] = co[0]; positions[currentIndex * 3 + 1] = co[2]; positions[currentIndex * 3 + 2] = -co[1]; } if (no) { normals[currentIndex * 3 + 0] = no[0]; normals[currentIndex * 3 + 1] = no[2]; normals[currentIndex * 3 + 2] = -no[1]; } else { computeNormals = true; } if (uv) { const uv1 = uv[index].uv; uvs[currentIndex * 2 + 0] = uv1[0]; uvs[currentIndex * 2 + 1] = uv1[1]; } currentIndex++; } indexi += 2; } } geometry.setAttribute("position", new ctx.BufferAttribute(positions, 3)); geometry.setIndex(new ctx.BufferAttribute(indices, 1)); geometry.setAttribute("normal", new ctx.BufferAttribute(normals, 3)); geometry.setAttribute("uv", new ctx.BufferAttribute(uvs, 2)); if (computeNormals) { geometry.computeVertexNormals(); } return geometry; } function createMaterial(mat, ctx) { const material = new ctx.MeshPhysicalMaterial(); material.color.setRGB(mat.r, mat.g, mat.b); material.roughness = mat.roughness !== void 0 ? mat.roughness : 0.4; material.metalness = mat.metallic !== void 0 ? mat.metallic : 0; material.opacity = 1; material.transparent = material.opacity < 1; return material; } function createMesh(object, loaded, ctx) { if (!object.data) { return void 0; } const geometry = loaded.get(object.data) ?? createBufferGeometry(object.data, ctx); loaded.set(object.data, geometry); const mat = object.data.mat[0]; const material = mat ? loaded.get(mat) ?? createMaterial(mat, ctx) : new ctx.MeshPhysicalMaterial(); if (mat) loaded.set(mat, material); const mesh = new ctx.Mesh(geometry, material); mesh.castShadow = true; mesh.receiveShadow = true; return mesh; } const blenderObjectTypes = { empty: 0, mesh: 1, lamp: 10, camera: 11 }; async function createObjects(file, ctx) { const objects = []; const childMap = /* @__PURE__ */ new Map(); const objMap = /* @__PURE__ */ new Map(); const blendObjects = file.objects.Object ?? []; const loaded = /* @__PURE__ */ new WeakMap(); for (const object of blendObjects) { if (loaded.has(object)) { console.warn("BlendLoader - duplicate object"); continue; } let obj = void 0; switch (object.type) { case blenderObjectTypes.empty: obj = new ctx.Object3D(); break; case blenderObjectTypes.mesh: obj = createMesh(object, loaded, ctx); break; case blenderObjectTypes.lamp: obj = createLight(object, ctx); break; case blenderObjectTypes.camera: obj = createCamera(object, ctx); break; default: obj = new ctx.Object3D(); console.warn("Unsupported object type", object.type, object, obj); } if (obj) { setTransform(object, obj); const exChildren = childMap.get(object); if (exChildren) { for (const exChild of exChildren) { obj.add(exChild); } childMap.delete(object); } objMap.set(object, obj); if (object.parent === object) { console.error("BlendLoader - invalid, parent same as object, adding to root", object); objects.push(obj); } else if (!object.parent) { objects.push(obj); } else { const parent = objMap.get(object.parent); if (!parent) { const chi = childMap.get(object.parent) ?? []; chi.push(obj); childMap.set(object, chi); } else { parent.add(obj); } } loaded.set(object, obj); } } if (childMap.size) { const vals = [...childMap.values()].flat(); console.warn("BlendLoader - unknown objects with parents, adding to root", vals, childMap); objects.push(...vals); } return objects.filter((o) => !!o); } const eulerModes = { [1]: "XYZ", [2]: "XZY", [3]: "YXZ", [4]: "YZX", [5]: "ZXY", [6]: "ZYX" }; function setTransform(object, obj) { obj.name = object.aname; obj.scale.set(object.size[0], object.size[2], object.size[1]); obj.position.set(object.loc[0], object.loc[2], -object.loc[1]); if (!object.rotmode || object.rotmode === 0) { obj.quaternion.set(object.quat[1], object.quat[3], -object.quat[2], object.quat[0]); } else { const order = eulerModes[object.rotmode] ?? "XYZ"; const quat = new Quaternion().setFromEuler(new Euler(object.rot[0], object.rot[1], object.rot[2], order.split("").reverse().join(""))); obj.quaternion.set(quat.x, quat.z, -quat.y, quat.w); } if (object.type === blenderObjectTypes.camera) obj.quaternion.multiply(new Quaternion().setFromAxisAngle(new Vector3(1, 0, 0), -Math.PI / 2)); obj.updateMatrix(); if (obj.setDirty) obj.setDirty(); } const _BlendLoadPlugin = class _BlendLoadPlugin extends BaseImporterPlugin { constructor() { super(); this._importer = new Importer(class extends FileLoader { async loadAsync(url, onProgress) { this.setResponseType("arraybuffer"); const res = await super.loadAsync(url, onProgress); const blend = await parseBlend(res); const ctx = { Object3D: Object3D2, Mesh: Mesh2, MeshPhysicalMaterial: PhysicalMaterial, PerspectiveCamera: PerspectiveCamera0, OrthographicCamera: OrthographicCamera0, PointLight: PointLight2, BufferGeometry: BufferGeometry2, BufferAttribute }; const objects = await createObjects(blend, ctx); const root = new Object3D(); root.add(...objects); blend.scene = root; return blend; } transform(res, options) { if (typeof options.onBlendLoad === "function") { options.onBlendLoad(res); } return res.scene; } }, ["blend"], ["application/x-blender"], true); } }; _BlendLoadPlugin.PluginType = "BlendLoadPlugin"; let BlendLoadPlugin = _BlendLoadPlugin; export { BlendLoadPlugin }; //# sourceMappingURL=index.mjs.map