import { MathUtils } from '../math/MathUtils';
import { BaseComponent } from './BaseComponent';
import { PickType } from '../const';
const __ = {
private: Symbol('private'),
}
let _position = MathUtils.createVec3();
let _origin = MathUtils.createVec3();
// #region Private Functions
// #endregion
/**
* @class CameraPickerComponent
* The camera picker compnent.
* @memberof THING
* @extends THING.BaseComponent
*/
class CameraPickerComponent extends BaseComponent {
/**
* 相机拾取组件,用于通过相机拾取场景中的对象。
*/
constructor() {
super();
this[__.private] = {};
let _private = this[__.private];
_private.pickFuncName = 'pickFromGPU';
_private.onPickObject = null;
_private.onPickCamera = null;
}
// #region Private
// Get adjusted position of screen to pick.
_getAdjustedPosition(x, y) {
let style = this.app.delegate.getAttribute('Style');
if (style) {
let transform = style.transform;
if (transform) {
let scale = transform.scale;
if (scale) {
x /= scale[0];
y /= scale[1];
}
}
}
return [x, y];
}
// Get the camera to use for picking operations.
_getPickingCamera() {
let _private = this[__.private];
let cameraObject = this.object;
if (_private.onPickCamera) {
cameraObject = _private.onPickCamera(this.object);
}
return cameraObject;
}
// #endregion
/**
* Check intersection between object and region.
* @param {Array<number>} region The [left, top, width, height] region in screen.
* @param {THING.Object3D} object The object.
* @param {boolean} included It fully includes the default false
* @returns {boolean} Whether the object intersects with the region.
*/
intersectObjectInRegion(region, object, included = false) {
let cameraObject = this._getPickingCamera();
if (!cameraObject) {
return false; // No intersection if callback returns null
}
let x = region[0];
let y = region[1];
let width = region[2];
let height = region[3];
return cameraObject.node.intersectNodeInRegion([x, y, width, height], object.bodyNode, null, included);
}
/**
* @public
* @typedef {object} PickResult
* @property {THING.BaseObject} object 拾取到的对象
* @property {Array<number>} position 拾取的位置
* @property {Array<number>} normal 拾取的法向量
* @property {number} pickedId 拾取的ID
* @property {object} external 拾取到的附加信息
*/
/**
* 选择节点。
* @param {number} x 屏幕上的 x 坐标。
* @param {number} y 屏幕上的 y 坐标。
* @param {THING.BaseObject} [root] 要选择的根节点对象。
* @returns {PickResult} 拾取结果对象,包含拾取到的对象和相关信息。
*/
pick(x, y, root) {
let _private = this[__.private];
let app = this.app;
// Get the camera to use for picking
let cameraObject = this._getPickingCamera();
if (!cameraObject) {
return null; // Pick nothing if callback returns null
}
let cameraNode = cameraObject.node;
let pickFuncName = _private.pickFuncName;
// Get screen position in pixel
let position = this._getAdjustedPosition(x, y);
// Get the pick scene
let pickScene = root ? root : app.scene.root;
// Pick object in scene
let result = cameraNode[pickFuncName](position, pickScene);
if (!result) {
return null;
}
// Get the picked object
let object = app.objectManager.getBaseObjectFromNode(result.node);
// Notify outside we had picked object
if (_private.onPickObject) {
// Create options object with lazy-loaded properties
let options = {};
Object.defineProperties(options, {
'_result': {
get() {
return result;
}
},
'node': {
get() {
return result.node;
}
},
'subNodeName': {
get() {
return result.subNodeName;
}
},
'normal': {
get() {
return result.normal;
}
},
'position': {
get() {
return result.position;
}
},
'pickedId': {
get() {
return result.pickedId;
}
}
});
object = _private.onPickObject(object, options);
if (!object) {
return null; // Pick nothing
}
}
// Build picked result
let ev = {
object,
subNodeName: result.subNodeName,
external: {}
};
// Get the picked Id
if (result.pickedId !== undefined) {
ev.pickedId = result.pickedId;
}
// To prevent BIG deal for these actions, we make property here to delay some operations
Object.defineProperties(ev, {
'position': {
get() {
return result.position;
}
},
'normal': {
get() {
return result.normal;
}
}
});
return ev;
}
/**
* Pick object from GPU.
* @returns {Promise} The promise object.
*/
waitForGPUUpdate() {
const cameraNode = this.object.node;
return cameraNode.waitForGPUUpdate();
}
/**
* Pick from cross planes.
* @param {number} x The x coordinate in screen.
* @param {number} y The y coordinate in screen.
* @param {boolean} isVertical Whether vertical and horizontal planes will always be vertical.
* @returns {PickResult} The pick result with position information.
*/
pickFromCrossPlanes(x, y, isVertical = true) {
let position = this.object.control.pickFromCrossPlanes(x, y, isVertical);
return {
position,
object: null,
pickedId: null,
external: null
};
}
/**
* Calculate the points where the plane intersects.
* @param {number} x The x coordinate in screen.
* @param {number} y The y coordinate in screen.
* @param {Array<number>} normal The plane normal. (default [0,1,0])
* @param {number} constant Distance of plane. (default 0)
* @returns {Array<number>} The intersection point coordinates.
*/
intersectPlane(x, y, normal = [0, 1, 0], constant = 0) {
let object = this.object;
let origin, direction;
if (object.projectionType === 'Orthographic') {
origin = object.screenToWorld([x, y, (object.near + object.far) / (object.near - object.far)]);
direction = MathUtils.transformDirection([0, 0, -1], object.matrixWorld);
}
else {
let position = object.screenToWorld([x, y, 0], _position);
origin = object.getWorldPosition(_origin);
direction = MathUtils.normalizeVector(MathUtils.subVector(position, origin));
}
return MathUtils.intersectPlane(origin, direction, normal, constant);
}
/**
* 获取/设置拾取类型。
* @type {PickType}
* @example
* THING.App.current.camera.pickType = THING.PickType.GPU;
* @public
*/
get pickType() {
let _private = this[__.private];
switch (_private.pickFuncName) {
case 'pickFromGPU':
return PickType.GPU;
case 'pickFromGPUFast':
return PickType.GPUFast;
case 'pick':
return PickType.Raycaster;
default:
break;
}
return null;
}
set pickType(value) {
let _private = this[__.private];
switch (value) {
case PickType.GPU:
_private.pickFuncName = 'pickFromGPU';
break;
case PickType.GPUFast:
_private.pickFuncName = 'pickFromGPUFast';
break;
case PickType.Raycaster:
_private.pickFuncName = 'pick';
break;
default:
break;
}
}
/**
* The function to call when pick object.
* @callback onPickObjectCallback
* @param {THING.Object3D} object The object.
* @param {object} options The pick result options with lazy-loaded properties.
* @param {*} options.node The picked node.
* @param {string} options.subNodeName The sub node name.
* @param {Array<number>} options.normal The pick normal vector.
* @param {Array<number>} options.position The pick position.
* @param {number} options.pickedId The picked ID.
* @returns {THING.Object3D} The latest picked object, if it's null indicates pick nothing
*/
/**
* Get/Set the callback function when pick object.
* @type {onPickObjectCallback}
* @private
*/
get onPickObject() {
let _private = this[__.private];
return _private.onPickObject;
}
set onPickObject(value) {
let _private = this[__.private];
_private.onPickObject = value;
}
/**
* The function to call when determining which camera to use for picking.
* @callback onPickCameraCallback
* @param {THING.Camera} currentCamera The current camera object.
* @returns {THING.Camera} The camera to use for picking, or null to skip picking.
*/
/**
* Get/Set the callback function when determining which camera to use for picking.
* @type {onPickCameraCallback}
* @public
* @example
* THING.App.current.camera.onPickCamera = function(currentCamera) {
* return currentCamera;
* }
*/
get onPickCamera() {
let _private = this[__.private];
return _private.onPickCamera;
}
set onPickCamera(value) {
let _private = this[__.private];
_private.onPickCamera = value;
}
}
CameraPickerComponent.exportProperties = [
'pickType',
'onPickCamera'
];
export { CameraPickerComponent }