import { BaseTickableObject3D, Utils } from '@uino/thing';
import { PLYLoader } from '../../loaders/PLYLoader';
import { BinaryLoader } from '../../loaders/BinaryLoader';
/**
* @class GaussianSplatting
* @summary 高斯喷射类,用于在三维场景中渲染 3D Gaussian Splatting 数据。
* 支持 .ply、.splat、.ksplat 三种格式的高斯喷射数据加载与实时渲染。
* 注意:这里的点击事件实际用的是浏览器的click,没有走thingjs的事件逻辑
* @public
* @author huyang
* @extends THING.BaseTickableObject3D
* @memberof THING
* @example
* // 加载并渲染高斯喷射数据
* const gs = new THING.EXTEND.GaussianSplatting({
* url: './models/scene.ply',
* onComplete: function() {
* console.log('高斯喷射数据加载完成');
* }
* });
* gs.position = [0, 0, 0];
*
* // 监听点击事件,获取拾取坐标
* gs.on('click', function(ev) {
* console.log('点击位置:', ev.pickedPosition);
* });
*/
class GaussianSplatting extends BaseTickableObject3D {
/**
* @public
* @summary 高斯喷射类,用于渲染高斯喷射数据
* @param {Object} param
* @param {String} param.url 数据url 支持后缀名为ply,splat,ksplat的高斯喷射数据
* @param {Function} [param.onComplete] 图层创建完毕后的回调函数
*/
constructor(param = {}) {
super(param);
}
_loadData(url, resolve, reject) {
const ext = url._getExtension();
let loader = null;
if (ext === 'splat' || ext === 'ksplat') {
loader = new BinaryLoader();
}
else if (ext === 'ply') {
loader = new PLYLoader();
}
else {
console.error('Url does not ends with splat,ksplat or ply');
reject();
return;
}
loader.load(url, (geometry) => {
resolve && resolve(geometry);
}, reject);
}
onSetupResource(param) {
this.onSetupURL(param);
super.onSetupResource(param);
}
onLoadRenderableResource(param, resolve, reject) {
if (this.resource.url) {
this._loadData(this.resource.url, (data) => {
const node = Utils.createObject('GaussianSplattingNode', { uScene: this.node._uScene, camera: this.app.camera.node._node, buffer: data });
this.body.setNode(node);
resolve && resolve();
}, reject)
}
}
onUpdate() {
if (this.node._mesh) {
this.node._mesh.update(this.app.camera.node._node, this.app.container.clientWidth, this.app.container.clientHeight);
}
}
off(type, condition, callback, tag, priority, options = {}) {
const info = Utils.parseEvent(
type,
condition,
callback,
tag,
priority,
options
);
if (this._clickCallback) {
if (info.type.toLowerCase() === 'click') {
const eventType = 'pointerup';
this.app.container.ownerDocument.removeEventListener(eventType, this._clickCallback, false);
}
else if (info.type.toLowerCase() === 'dblclick') {
const eventType = 'dblclick';
this.app.container.ownerDocument.removeEventListener(eventType, this._clickCallback, false);
}
}
}
// 重写on方法 支持拾取对象的id 坐标和userData
on(type, condition, callback, tag, priority, options = {}) {
let result;
const info = Utils.parseEvent(
type,
condition,
callback,
tag,
priority,
options
);
// options = { useCapture: true };
const oldCallback = info.callback;
this._clickCallback = (event) => {
event.object = this;
Object.defineProperties(event, {
'pickedPosition': {
get() {
const mouse = {};
mouse.x = (event.clientX / app.container.clientWidth) * 2 - 1;
mouse.y = -(event.clientY / app.container.clientHeight) * 2 + 1;
const position = event.object.app.camera.picker._getAdjustedPosition(mouse.x, mouse.y);
const pickedPosition = event.object.node.getPickedPosition(position[0], position[1]);
// if (pickedPosition) {
return pickedPosition;
// }
}
},
});
oldCallback(event);
};
this._clickCallback = this._clickCallback.bind(this);
if (info.type.toLowerCase() === 'click') {
const eventType = 'pointerup';
result = this.app.container.ownerDocument.addEventListener(eventType, this._clickCallback, false);
}
else if (info.type.toLowerCase() === 'dblclick') {
const eventType = 'dblclick';
result = this.app.container.ownerDocument.addEventListener(eventType, this._clickCallback, false);
}
return result;
}
}
export { GaussianSplatting };