@threepipe/plugin-blend-importer
Version:
Basic importer for .blend file
1,042 lines (1,040 loc) • 39.7 kB
JavaScript
(function(global, factory) {
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("threepipe")) : typeof define === "function" && define.amd ? define(["exports", "threepipe"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["@threepipe/plugin-blend-importer"] = {}, global.threepipe));
})(this, function(exports2, threepipe) {
"use strict";/**
* @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.
*/
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 self2 = 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();
self2.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;
}
}
};
self2.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 threepipe.Quaternion().setFromEuler(new threepipe.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 threepipe.Quaternion().setFromAxisAngle(new threepipe.Vector3(1, 0, 0), -Math.PI / 2));
obj.updateMatrix();
if (obj.setDirty) obj.setDirty();
}
const _BlendLoadPlugin = class _BlendLoadPlugin extends threepipe.BaseImporterPlugin {
constructor() {
super();
this._importer = new threepipe.Importer(class extends threepipe.FileLoader {
async loadAsync(url, onProgress) {
this.setResponseType("arraybuffer");
const res = await super.loadAsync(url, onProgress);
const blend = await parseBlend(res);
const ctx = {
Object3D: threepipe.Object3D2,
Mesh: threepipe.Mesh2,
MeshPhysicalMaterial: threepipe.PhysicalMaterial,
PerspectiveCamera: threepipe.PerspectiveCamera0,
OrthographicCamera: threepipe.OrthographicCamera0,
PointLight: threepipe.PointLight2,
BufferGeometry: threepipe.BufferGeometry2,
BufferAttribute: threepipe.BufferAttribute
};
const objects = await createObjects(blend, ctx);
const root = new threepipe.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;
exports2.BlendLoadPlugin = BlendLoadPlugin;
Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
});
//# sourceMappingURL=index.js.map