@awayjs/view
Version:
View for AwayJS
407 lines (406 loc) • 19 kB
JavaScript
import { __extends } from "tslib";
import { Vector3D, Matrix3D, Box, AbstractionBase, Point, } 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", {
// private _orientedBoxBounds: Box[] = [];
// private _orientedBoxBoundsDirty: boolean[] = [true, true];
// private _orientedSphereBounds: Sphere[] = [];
// private _orientedSphereBoundsDirty: boolean[] = [true, true];
/**
*
* @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()[2];
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()[2];
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._boundingVolumePools = {};
};
BoundsPicker.prototype.onInvalidate = function () {
_super.prototype.onInvalidate.call(this);
// this._orientedBoxBoundsDirty[0] = true;
// this._orientedBoxBoundsDirty[1] = true;
// this._orientedSphereBoundsDirty[0] = true;
// this._orientedSphereBoundsDirty[1] = true;
for (var key in this._boundingVolumePools)
this._boundingVolumePools[key].abstractions.forEach(function (boundingVolume) { return boundingVolume.onInvalidate(); });
};
BoundsPicker.prototype.traverse = function () {
this._invalid = false;
this._boundsPickers.length = 0;
this._asset.acceptTraverser(this);
};
BoundsPicker.prototype.getTraverser = function (node) {
// const traverser: BoundsPicker = this._pickGroup.getBoundsPicker(node);
// this._boundsPickers.push(traverser);
return this;
};
/**
* 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 !(node.container.assetType == '[asset TextSprite]');
};
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 pool.abstractions.getAbstraction(target);
};
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.hitTestPoint = function (x, y, shapeFlag) {
if (shapeFlag === void 0) { shapeFlag = false; }
return this._hitTestPointInternal(this._asset, x, y, shapeFlag, false);
};
BoundsPicker.prototype._hitTestPointInternal = function (rootNode, x, y, shapeFlag, maskFlag) {
if (shapeFlag === void 0) { shapeFlag = false; }
if (maskFlag === void 0) { maskFlag = false; }
var node = this._asset;
if (node.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);
node.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 ||
node.container.assetType == '[asset TextField]' ||
node.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(rootNode, 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) {
var node = this._asset;
//TODO: getBoxBounds should be using the root partition root
//first do a fast box comparision
var objBox = obj.getBoxBounds(node, true, true);
if (objBox == null)
return false;
var box = this.getBoxBounds(node, 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(node, true).intersects(this.getBoxBounds(node, true));
};
BoundsPicker.prototype._getBoxBoundsInternal = function (invTargetMatrix, strokeFlag, fastFlag, cache, target) {
if (invTargetMatrix === void 0) { invTargetMatrix = 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 node = this._asset;
var m = new Matrix3D();
var matrix3D = void 0;
// if (fastFlag) {
// let obb: Box;
// const strokeIndex: number = strokeFlag ? 1 : 0;
// const invMatrix3D = (<ContainerNode> this._asset).getInverseMatrix3D().clone();
// if (invTargetMatrix) { // a null invTargetMatrix means local coords to the node so matrix3D is identity
// matrix3D = (<ContainerNode> this._asset).getMatrix3D().clone()
// matrix3D.append(invTargetMatrix);
// }
// if (this._orientedBoxBoundsDirty[strokeIndex]) {
// this._orientedBoxBoundsDirty[strokeIndex] = false;
// for (let i: number = 0; i < numPickers; ++i) {
// obb = this._boundsPickers[i]
// ._getBoxBoundsInternal(
// this._boundsPickers[i].node != node
// ? invMatrix3D
// : null,
// strokeFlag,
// fastFlag,
// this._orientedBoxBounds[strokeIndex],
// obb);
// }
// this._orientedBoxBounds[strokeIndex] = obb;
// } else {
// obb = this._orientedBoxBounds[strokeIndex];
// }
// if (obb != null) {
// target = (matrix3D)
// ? matrix3D.transformBox(obb).union(target, target || cache)
// : obb.union(target, target || cache);
// }
// } else {
matrix3D = invTargetMatrix ? invTargetMatrix : this._asset.getInverseMatrix3D();
for (var i = 0; i < numPickers; ++i)
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 node = this._asset;
var m = new Matrix3D();
for (var i = 0; i < numPickers; ++i) {
if (this._boundsPickers[i].node != node) {
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 (rootNode, planes, numPlanes) {
return this.getBoundingVolume(rootNode).isInFrustum(planes, numPlanes);
};
BoundsPicker.prototype.onClear = function () {
_super.prototype.onClear.call(this);
for (var key in this._boundingVolumePools)
this._boundingVolumePools[key].abstractions.forEach(function (boundingVolume) { return boundingVolume.onClear(); });
this._boundingVolumePools = null;
this._boundsPickers.length = 0;
// this._orientedBoxBoundsDirty[0] = true;
// this._orientedBoxBoundsDirty[1] = true;
// this._orientedSphereBoundsDirty[0] = true;
// this._orientedSphereBoundsDirty[1] = true;
};
/**
*
* @param entity
*/
BoundsPicker.prototype.applyEntity = function (node) {
var _a;
if (node.container.getEntity())
this._boundsPickers.push(this._pickGroup.abstractions.getAbstraction(node));
else
//check if we have a PickEntity abstraction and if so, clear it!
(_a = this._pickGroup.abstractions.checkAbstraction(node)) === null || _a === void 0 ? void 0 : _a.onClear();
};
BoundsPicker.tmpMatrix = new Matrix3D();
BoundsPicker.tmpPoint = new Point();
BoundsPicker.tmpBox = new Box();
BoundsPicker.MINIMAL_SCALE = 0.00001;
return BoundsPicker;
}(AbstractionBase));
export { BoundsPicker };