UNPKG

@awayjs/view

Version:
374 lines (373 loc) 17.4 kB
import { __extends } from "tslib"; import { Vector3D, Matrix3D, Box, AbstractionBase, Point, WeakAssetSet } from '@awayjs/core'; import { BoundingVolumePool } from '../bounds/BoundingVolumePool'; import { BoundingVolumeType } from '../bounds/BoundingVolumeType'; /** * Picks a 3d object from a view or scene by 3D raycast calculations. * Performs an initial coarse boundary calculation to return a subset * of entities whose bounding volumes intersect with the specified ray, * then triggers an optional picking collider on individual renderable * objects to further determine the precise values of the picking ray collision. * * @class away.pick.RaycastPicker */ var BoundsPicker = /** @class */ (function (_super) { __extends(BoundsPicker, _super); function BoundsPicker() { var _this = _super !== null && _super.apply(this, arguments) || this; _this._boundsPickers = []; return _this; } Object.defineProperty(BoundsPicker.prototype, "node", { /** * * @returns {ContainerNode} */ get: function () { return this._asset; }, enumerable: false, configurable: true }); Object.defineProperty(BoundsPicker.prototype, "width", { /** * Indicates the width of the display object, in pixels. The width is * calculated based on the bounds of the content of the display object. When * you set the <code>width</code> property, the <code>scaleX</code> property * is adjusted accordingly, as shown in the following code: * * <p>Except for TextField and Video objects, a display object with no * content(such as an empty sprite) has a width of 0, even if you try to set * <code>width</code> to a different value.</p> */ get: function () { var box = this.getBoxBounds(); if (box == null) return 0; // scale already should be applied, because we request width relative self return box.width; }, set: function (val) { var transform = this._asset.container.transform; var selfBox = this.getBoxBounds(); //return if box is empty ie setting width for no content is impossible if (selfBox == null) return; var rotation = transform.rotation; var baseMatrix = transform.matrix3D; var box = baseMatrix.transformBox(selfBox, BoundsPicker.tmpBox); var scaleFactor = box.width > 0 ? val / box.width : 1; // without rotation, fast case if (rotation.z === 0) { var s = transform.scale; transform.scaleTo(s.x * scaleFactor || BoundsPicker.MINIMAL_SCALE, s.y, s.z); return; } var matrix = BoundsPicker.tmpMatrix; matrix.copyFrom(baseMatrix); matrix.appendScale(scaleFactor || BoundsPicker.MINIMAL_SCALE, 1, 1); // decompose matrix for grabbing transformed scale of transform // this is target scale that applied (real?) by width var realScale = matrix.decompose()[3]; transform.scaleTo(realScale.x, realScale.y, realScale.z); }, enumerable: false, configurable: true }); Object.defineProperty(BoundsPicker.prototype, "height", { /** * Indicates the height of the display object, in pixels. The height is * calculated based on the bounds of the content of the display object. When * you set the <code>height</code> property, the <code>scaleY</code> property * is adjusted accordingly, as shown in the following code: * * <p>Except for TextField and Video objects, a display object with no * content (such as an empty sprite) has a height of 0, even if you try to * set <code>height</code> to a different value.</p> */ get: function () { var box = this.getBoxBounds(); if (box == null) return 0; // if (this._node._registrationMatrix3D) // return box.height*this._node.scaleY*this._node._registrationMatrix3D._rawData[5]; // already should be applied return box.height; // * this._node.container.transform.scale.y; }, set: function (val) { var transform = this._asset.container.transform; var selfBox = this.getBoxBounds(); //return if box is empty ie setting height for no content is impossible if (selfBox == null) return; var baseMatrix = transform.matrix3D; var rotation = transform.rotation; var box = baseMatrix.transformBox(selfBox, BoundsPicker.tmpBox); var scaleFactor = box.height > 0 ? val / box.height : 1; // without rotation, fast case if (rotation.z === 0) { var s = transform.scale; transform.scaleTo(s.x, s.y * scaleFactor || BoundsPicker.MINIMAL_SCALE, s.z); return; } // or we should use decomposition var matrix = BoundsPicker.tmpMatrix; matrix.copyFrom(baseMatrix); matrix.appendScale(1, scaleFactor || BoundsPicker.MINIMAL_SCALE, 1); var realScale = matrix.decompose()[3]; transform.scaleTo(realScale.x, realScale.y, realScale.z); }, enumerable: false, configurable: true }); Object.defineProperty(BoundsPicker.prototype, "depth", { /** * Indicates the depth of the display object, in pixels. The depth is * calculated based on the bounds of the content of the display object. When * you set the <code>depth</code> property, the <code>scaleZ</code> property * is adjusted accordingly, as shown in the following code: * * <p>Except for TextField and Video objects, a display object with no * content (such as an empty sprite) has a depth of 0, even if you try to * set <code>depth</code> to a different value.</p> */ get: function () { var box = this.getBoxBounds(); if (box == null) return 0; // if (this._node._registrationMatrix3D) // return box.depth*this._node.scaleZ*this._node._registrationMatrix3D._rawData[10]; return box.depth * this._asset.container.transform.scale.z; }, set: function (val) { var box = this.getBoxBounds(); //return if box is empty ie setting depth for no content is impossible if (box == null || box.depth == 0) return; //this._updateAbsoluteDimension(); var container = this._asset.container; container.transform.scaleTo(container.transform.scale.x, container.transform.scale.y, val / box.depth); }, enumerable: false, configurable: true }); BoundsPicker.prototype.init = function (node, pool) { _super.prototype.init.call(this, node, pool); this._pickGroup = pool.pickGroup; this._boundingVolumes = new WeakAssetSet('BoundingVolumeBase'); this._boundingVolumePools = {}; }; BoundsPicker.prototype.onInvalidate = function (event) { _super.prototype.onInvalidate.call(this, event); this._boundingVolumes.forEach(function (boundingVolume) { return boundingVolume.onInvalidate(event); }); }; BoundsPicker.prototype.traverse = function () { this._invalid = false; this._boundsPickers.length = 0; this._asset.acceptTraverser(this); }; BoundsPicker.prototype.getTraverser = function (node) { var traverser = this._pickGroup.getBoundsPicker(node); this._boundsPickers.push(traverser); return traverser; }; /** * Returns true if the current node is at least partly in the frustum. * If so, the partition node knows to pass on the traverser to its children. * * @param node The Partition3DNode object to frustum-test. */ BoundsPicker.prototype.enterNode = function (node) { return true; }; BoundsPicker.prototype.getBoundingVolume = function (target, type) { if (target === void 0) { target = null; } if (type === void 0) { type = null; } if (target == null) target = this._asset; if (type == null) type = this._asset.container.defaultBoundingVolume; var pool = this._boundingVolumePools[type] || (this._boundingVolumePools[type] = new BoundingVolumePool(this, type)); return target.getAbstraction(pool); }; BoundsPicker.prototype.getBoxBounds = function (targetCoordinateSpace, strokeFlag, fastFlag) { if (targetCoordinateSpace === void 0) { targetCoordinateSpace = null; } if (strokeFlag === void 0) { strokeFlag = false; } if (fastFlag === void 0) { fastFlag = false; } return this.getBoundingVolume(targetCoordinateSpace, strokeFlag ? (fastFlag ? BoundingVolumeType.BOX_BOUNDS_FAST : BoundingVolumeType.BOX_BOUNDS) : (fastFlag ? BoundingVolumeType.BOX_FAST : BoundingVolumeType.BOX)).getBox(); }; BoundsPicker.prototype.getSphereBounds = function (targetCoordinateSpace, strokeFlag, fastFlag) { if (targetCoordinateSpace === void 0) { targetCoordinateSpace = null; } if (strokeFlag === void 0) { strokeFlag = false; } if (fastFlag === void 0) { fastFlag = false; } return this.getBoundingVolume(targetCoordinateSpace, strokeFlag ? (fastFlag ? BoundingVolumeType.SPHERE_BOUNDS_FAST : BoundingVolumeType.SPHERE_BOUNDS) : (fastFlag ? BoundingVolumeType.SPHERE_FAST : BoundingVolumeType.SPHERE)).getSphere(); }; BoundsPicker.prototype.addBoundingVolume = function (boundingVolume) { this._boundingVolumes.add(boundingVolume); }; BoundsPicker.prototype.removeBoundingVolume = function (boundingVolume) { this._boundingVolumes.remove(boundingVolume); }; BoundsPicker.prototype.hitTestPoint = function (x, y, shapeFlag) { if (shapeFlag === void 0) { shapeFlag = false; } return this._hitTestPointInternal(this._asset, x, y, shapeFlag, false); }; BoundsPicker.prototype._hitTestPointInternal = function (node, x, y, shapeFlag, maskFlag) { if (shapeFlag === void 0) { shapeFlag = false; } if (maskFlag === void 0) { maskFlag = false; } if (this._asset.getMaskId() != -1 && (!maskFlag || !shapeFlag)) //allow masks for bounds hit tests return false; if (this._invalid) this.traverse(); //set local tempPoint for later reference var tempPoint = BoundsPicker.tmpPoint; tempPoint.setTo(x, y); this._asset.globalToLocal(tempPoint, tempPoint); //early out for box test var box = this.getBoxBounds(null, false, true); if (box == null || !box.contains(tempPoint.x, tempPoint.y, 0)) return false; //early out for non-shape tests if (!shapeFlag || this._asset.container.assetType == '[asset TextField]' || this._asset.container.assetType == '[asset Billboard]') return true; var numPickers = this._boundsPickers.length; if (numPickers) for (var i = 0; i < numPickers; ++i) if (this._boundsPickers[i]._hitTestPointInternal(node, x, y, shapeFlag, maskFlag)) return true; return false; }; /** * Evaluates the bounding box of the display object to see if it overlaps or * intersects with the bounding box of the <code>obj</code> display object. * * @param obj The display object to test against. * @return <code>true</code> if the bounding boxes of the display objects * intersect; <code>false</code> if not. */ BoundsPicker.prototype.hitTestObject = function (obj) { //TODO: getBoxBounds should be using the root partition root //first do a fast box comparision var objBox = obj.getBoxBounds(this._asset, true, true); if (objBox == null) return false; var box = this.getBoxBounds(this._asset, true, true); if (box == null) return false; if (!objBox.intersects(box)) return false; //if the fast box passes, do the slow test return obj.getBoxBounds(this._asset, true).intersects(this.getBoxBounds(this._asset, true)); }; BoundsPicker.prototype._getBoxBoundsInternal = function (matrix3D, strokeFlag, fastFlag, cache, target) { if (matrix3D === void 0) { matrix3D = null; } if (strokeFlag === void 0) { strokeFlag = true; } if (fastFlag === void 0) { fastFlag = true; } if (cache === void 0) { cache = null; } if (target === void 0) { target = null; } if (this._invalid) this.traverse(); var numPickers = this._boundsPickers.length; if (numPickers > 0) { var m = new Matrix3D(); for (var i = 0; i < numPickers; ++i) { if (this._boundsPickers[i].node != this._asset) { if (matrix3D) m.copyFrom(matrix3D); else m.identity(); m.prepend(this._boundsPickers[i].node.container.transform.matrix3D); if (this._boundsPickers[i].node.container._registrationMatrix3D) m.prepend(this._boundsPickers[i].node.container._registrationMatrix3D); target = this._boundsPickers[i]._getBoxBoundsInternal(m, strokeFlag, fastFlag, cache, target); } else { target = this._boundsPickers[i]._getBoxBoundsInternal(matrix3D, strokeFlag, fastFlag, cache, target); } } } return target; }; BoundsPicker.prototype._getSphereBoundsInternal = function (center, matrix3D, strokeFlag, fastFlag, cache, target) { if (center === void 0) { center = null; } if (matrix3D === void 0) { matrix3D = null; } if (strokeFlag === void 0) { strokeFlag = true; } if (fastFlag === void 0) { fastFlag = true; } if (cache === void 0) { cache = null; } if (target === void 0) { target = null; } if (this._invalid) this.traverse(); var box = this._getBoxBoundsInternal(matrix3D, strokeFlag, fastFlag); if (box == null) return; if (!center) { center = new Vector3D(); center.x = box.x + box.width / 2; center.y = box.y + box.height / 2; center.z = box.z + box.depth / 2; } var numPickers = this._boundsPickers.length; if (numPickers > 0) { var m = new Matrix3D(); for (var i = 0; i < numPickers; ++i) { if (this._boundsPickers[i].node != this._asset) { if (matrix3D) m.copyFrom(matrix3D); else m.identity(); m.prepend(this._boundsPickers[i].node.container.transform.matrix3D); if (this._boundsPickers[i].node.container._registrationMatrix3D) m.prepend(this._boundsPickers[i].node.container._registrationMatrix3D); target = this._boundsPickers[i]._getSphereBoundsInternal(center, m, strokeFlag, fastFlag, cache, target); } else { target = this._boundsPickers[i]._getSphereBoundsInternal(center, matrix3D, strokeFlag, fastFlag, cache, target); } } } return target; }; /** * * @param planes * @param numPlanes * @returns {boolean} */ BoundsPicker.prototype.isInFrustum = function (planes, numPlanes) { return this._isInFrustumInternal(this._asset, planes, numPlanes); }; BoundsPicker.prototype._isInFrustumInternal = function (node, planes, numPlanes) { return this.getBoundingVolume(node).isInFrustum(planes, numPlanes); }; BoundsPicker.prototype.onClear = function (event) { _super.prototype.onClear.call(this, event); this._boundingVolumes.forEach(function (boundingVolume) { return boundingVolume.onClear(event); }); this._boundingVolumePools = null; this._boundsPickers.length = 0; }; /** * * @param entity */ BoundsPicker.prototype.applyEntity = function (node) { var _a; if (node.container.getEntity()) this._boundsPickers.push(node.getAbstraction(this._pickGroup)); else //check if we have a PickEntity abstraction and if so, clear it! (_a = node.checkAbstraction(this._pickGroup)) === null || _a === void 0 ? void 0 : _a.onClear(null); }; BoundsPicker.tmpMatrix = new Matrix3D(); BoundsPicker.tmpPoint = new Point(); BoundsPicker.tmpBox = new Box(); BoundsPicker.MINIMAL_SCALE = 0.00001; return BoundsPicker; }(AbstractionBase)); export { BoundsPicker };