UNPKG

@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
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