@needle-tools/engine
Version:
Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.
247 lines • 10.2 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { Object3D, Vector3 } from "three";
import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js';
import { SerializationContext } from "../../../engine/engine_serialization_core.js";
import { serializable } from "../../../engine/engine_serialization_decorator.js";
import { getWorldPosition } from "../../../engine/engine_three_utils.js";
import { getParam } from "../../../engine/engine_utils.js";
import { RenderTextureWriter } from "../../../engine/export/gltf/Writers.js";
import { shouldExport_HideFlags } from "../../../engine/export/utils.js";
import { registerExportExtensions } from "../../../engine/extensions/index.js";
import { NEEDLE_components } from "../../../engine/extensions/NEEDLE_components.js";
import GLTFMeshGPUInstancingExtension from '../../../include/three/EXT_mesh_gpu_instancing_exporter.js';
import { BoxHelperComponent } from "../../BoxHelperComponent.js";
import { Behaviour, GameObject } from "../../Component.js";
import { Renderer } from "../../Renderer.js";
const debugExport = getParam("debuggltfexport");
export const componentsArrayExportKey = "$___Export_Components";
// @generate-component
export class GltfExportBox extends BoxHelperComponent {
sceneRoot;
}
/**
* @category Asset Management
* @group Components
*/
export class GltfExport extends Behaviour {
binary = true;
objects = [];
ext;
async exportNow(name, opts) {
if (debugExport)
console.log("Exporting objects as glTF", this.objects);
if (!name)
name = "scene";
if (!this.objects || this.objects.length <= 0)
this.objects = [this.context.scene];
const options = {
binary: this.binary,
pivot: GltfExport.calculateCenter(this.objects),
...opts
};
const res = await this.export(this.objects, options).catch(err => {
console.error(err);
return false;
});
if (res === false)
return false;
if (!this.binary) {
if (!name.endsWith(".gltf"))
name += ".gltf";
}
else if (!name.endsWith(".glb"))
name += ".glb";
if (this.binary)
GltfExport.saveArrayBuffer(res, name);
else
GltfExport.saveJson(res, name);
return true;
}
async export(objectsToExport, opts) {
// -----------------------
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// TODO: refactor this to use ../engine/export/index exportAsGLTF function
// TODO add filtering / tags for what to export and what not < this is implemented in exportAsGLTF, see TODO above
if (!objectsToExport || objectsToExport.length <= 0) {
console.warn("No objects set to export");
return;
}
// Instantiate a exporter
const exporter = new GLTFExporter();
exporter.register(writer => new GLTFMeshGPUInstancingExtension(writer));
exporter.register(writer => new RenderTextureWriter(writer));
registerExportExtensions(exporter, this.context);
GltfExport.filterTopmostParent(objectsToExport);
// https://threejs.org/docs/#examples/en/exporters/GLTFExporter
const options = {
trs: false,
onlyVisible: true,
truncateDrawRange: false,
binary: true,
maxTextureSize: Infinity,
embedImages: true,
includeCustomExtensions: true,
animations: opts?.animations || GltfExport.collectAnimations(objectsToExport),
...opts
};
const undo = new Array();
const exportScene = new Object3D();
// set the pivot position
if (opts?.pivot)
exportScene.position.sub(opts.pivot);
// console.log(exportScene.position);
// add objects for export
if (debugExport)
console.log("EXPORT", objectsToExport);
objectsToExport.forEach(obj => {
if (obj && shouldExport_HideFlags(obj)) {
// adding directly does not require us to change parents and mess with the hierarchy actually
exportScene.children.push(obj);
// TODO: we should probably be doing this before writing nodes?? apply world scale, position, rotation etc for export only
obj.matrixAutoUpdate = false;
obj.matrix.copy(obj.matrixWorld);
// disable instancing
GameObject.getComponentsInChildren(obj, Renderer).forEach(r => {
if (GameObject.isActiveInHierarchy(r.gameObject))
r.setInstancingEnabled(false);
});
obj.traverse(o => {
if (!shouldExport_HideFlags(o)) {
const parent = o.parent;
o.removeFromParent();
undo.push(() => {
if (parent)
parent.add(o);
});
}
});
}
});
const serializationContext = new SerializationContext(exportScene);
if (opts?.needleComponents) {
this.ext = new NEEDLE_components();
}
if (this.ext) {
this.ext.registerExport(exporter);
this.ext.context = serializationContext;
}
return new Promise((resolve, reject) => {
if (debugExport)
console.log("Starting glTF export.");
try {
// Parse the input and generate the glTF output
exporter?.parse(exportScene,
// called when the gltf has been generated
res => {
cleanup();
resolve(res);
},
// called when there is an error in the generation
err => {
cleanup();
reject(err);
}, options);
}
catch (err) {
console.error(err);
reject(err);
}
finally {
undo.forEach(u => u());
if (debugExport)
console.log("Finished glTF export.");
}
});
function cleanup() {
objectsToExport.forEach(obj => {
if (!obj)
return;
obj.matrixAutoUpdate = true;
GameObject.getComponentsInChildren(obj, Renderer).forEach(r => {
if (GameObject.isActiveInHierarchy(r.gameObject))
r.setInstancingEnabled(false);
});
});
}
}
;
static saveArrayBuffer(buffer, filename) {
this.save(new Blob([buffer], { type: 'application/octet-stream' }), filename);
}
static saveJson(json, filename) {
this.save("data: text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(json)), filename);
}
static save(blob, filename) {
const link = document.createElement('a');
link.style.display = 'none';
document.body.appendChild(link); // Firefox workaround, see #6594
if (typeof blob === "string")
link.href = blob;
else
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
link.remove();
// console.log(link.href);
// URL.revokeObjectURL( url ); breaks Firefox...
}
static collectAnimations(objs, target) {
target = target || [];
for (const obj of objs) {
if (!obj)
continue;
obj.traverseVisible(o => {
if (o.animations && o.animations.length > 0)
target.push(...o.animations);
});
}
return target;
}
static calculateCenter(objs, target) {
const center = target || new Vector3();
center.set(0, 0, 0);
objs.forEach(obj => {
center.add(getWorldPosition(obj));
});
center.divideScalar(objs.length);
return center;
}
static filterTopmostParent(objs) {
if (objs.length <= 0)
return;
for (let index = 0; index < objs.length; index++) {
let obj = objs[index];
if (!obj) {
objs.splice(index, 1);
index--;
continue;
}
// loop hierarchy up and kick object if any of its parents is already in this list
// because then this object will already be exported (and we dont want to export it)
while (obj.parent) {
if (objs.includes(obj.parent)) {
// console.log("FILTER", objs[index]);
objs.splice(index, 1);
index--;
break;
}
obj = obj.parent;
}
}
}
}
__decorate([
serializable()
], GltfExport.prototype, "binary", void 0);
__decorate([
serializable(Object3D)
], GltfExport.prototype, "objects", void 0);
//# sourceMappingURL=GltfExport.js.map