UNPKG

@awayjs/view

Version:
301 lines (300 loc) 14.1 kB
import { __extends } from "tslib"; import { Vector3D, AbstractionBase } from '@awayjs/core'; /** * 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 RaycastPicker = /** @class */ (function (_super) { __extends(RaycastPicker, _super); function RaycastPicker() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.shapeFlag = false; _this.findClosestCollision = false; _this._entities = []; _this._pickers = []; _this._collectedEntities = []; return _this; } Object.defineProperty(RaycastPicker.prototype, "node", { get: function () { return this._asset; }, enumerable: false, configurable: true }); RaycastPicker.prototype.init = function (node, pool) { _super.prototype.init.call(this, node, pool); this.pickGroup = pool.pickGroup; }; RaycastPicker.prototype.onClear = function () { _super.prototype.onClear.call(this); this._dragNode = null; this._rootNode = null; this._entities.length = 0; this._pickers.length = 0; this._collectedEntities.length = 0; this.pickGroup = null; }; RaycastPicker.prototype.traverse = function () { this._entities.length = 0; this._pickers.length = 0; this._asset.acceptTraverser(this); }; RaycastPicker.prototype.getTraverser = function (node) { if (!node.isMouseDisabled() || node.isDragEntity()) { var traverser = this.pickGroup.getRaycastPicker(node); if (traverser._isIntersectingRayInternal(this._rootNode, this._globalRayPosition, this._globalRayDirection, this._shapeFlag)) { this._pickers.push(traverser); } return traverser; } return this; }; Object.defineProperty(RaycastPicker.prototype, "dragNode", { get: function () { return this._dragNode; }, set: function (node) { if (this._dragNode == node) return; if (this._dragNode) this._dragNode.stopDrag(); this._dragNode = node; if (this._dragNode) this._dragNode.startDrag(); }, enumerable: false, configurable: true }); /** * 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. */ RaycastPicker.prototype.enterNode = function (node) { if ((node.isInvisible() && node.getMaskId() == -1) || node.getMaskId() != this._rootNode.getMaskId()) return false; if (node.pickObjectNode) node.pickObjectNode.acceptTraverser(this); return true; // return node.isIntersectingRay( // this._rootNode, this._globalRayPosition, this._globalRayDirection, this.pickGroup); }; /** * @inheritDoc */ RaycastPicker.prototype.isIntersectingRay = function (globalRayPosition, globalRayDirection, shapeFlag) { if (shapeFlag === void 0) { shapeFlag = false; } return this._isIntersectingRayInternal(this._asset, globalRayPosition, globalRayDirection, shapeFlag); }; /** * @inheritDoc */ RaycastPicker.prototype._isIntersectingRayInternal = function (rootNode, globalRayPosition, globalRayDirection, shapeFlag) { this._rootNode = rootNode; this._globalRayPosition = globalRayPosition; this._globalRayDirection = globalRayDirection; this._shapeFlag = this.shapeFlag || shapeFlag; this.traverse(); if (!this._entities.length && !this._pickers.length) return false; // this._pickingCollision.rayPosition = this._entity.transform.inverseConcatenatedMatrix3D.transformVector(globalRayPosition, this._pickingCollision.rayPosition); // this._pickingCollision.rayDirection = this._entity.transform.inverseConcatenatedMatrix3D.deltaTransformVector(globalRayDirection, this._pickingCollision.rayDirection); // this._pickingCollision.normal = this._pickingCollision.normal || new Vector3D(); // var rayEntryDistance:number = this._pickGroup.getBoundsPicker(this._partition).getBoundingVolume().rayIntersection(this._pickingCollision.rayPosition, this._pickingCollision.rayDirection, this._pickingCollision.normal); // if (rayEntryDistance < 0) // return false; // this._pickingCollision.rayEntryDistance = rayEntryDistance; // this._pickingCollision.rayOriginIsInsideBounds = rayEntryDistance == 0; return true; }; // public isIntersectingShape(findClosestCollision:boolean):boolean // { // //recalculates the rayEntryDistance and normal for shapes // var rayEntryDistance:number = Number.MAX_VALUE; // for (var i:number = 0; i < this._entities.length; ++i) { // if (this._entities[i].isIntersectingShape(findClosestCollision) && rayEntryDistance > this._entities[i].pickingCollision.rayEntryDistance) { // rayEntryDistance = this._entities[i].pickingCollision.rayEntryDistance; // this._pickingCollision.normal = this._entities[i].pickingCollision.normal; // } // } // if (rayEntryDistance == Number.MAX_VALUE) { // this._pickingCollision.rayEntryDistance = -1; // return false; // } // this._pickingCollision.rayEntryDistance = rayEntryDistance; // return true; // } /** * @inheritDoc */ RaycastPicker.prototype.getCollision = function (rayPosition, rayDirection, shapeFlag, startingCollision) { if (shapeFlag === void 0) { shapeFlag = false; } if (startingCollision === void 0) { startingCollision = null; } return this._getCollisionInternal(rayPosition, rayDirection, shapeFlag, false, startingCollision); }; RaycastPicker.prototype.getViewCollision = function (x, y, shapeFlag, startingCollision) { if (shapeFlag === void 0) { shapeFlag = false; } if (startingCollision === void 0) { startingCollision = null; } var view = this._asset.view; //update ray var rayPosition = view.unproject(x, y, 0, RaycastPicker._rayPosition); var rayDirection = view.unproject(x, y, 1, RaycastPicker._rayDirection); // decrementBy is non-alloc method instead of substract rayDirection.decrementBy(rayPosition); return this._getCollisionInternal(rayPosition, rayDirection, shapeFlag, false, startingCollision); }; RaycastPicker.prototype._getCollisionInternal = function (rayPosition, rayDirection, shapeFlag, maskFlag, startingCollision) { //early out if no collisions detected if (!this._isIntersectingRayInternal(this._asset, rayPosition, rayDirection, shapeFlag)) return null; //collect pickers this._collectEntities(this._collectedEntities, this._dragNode); //console.log("entities: ", this._entities) var collision = this._getPickingCollision(startingCollision); //discard collected pickers this._collectedEntities.length = 0; return collision; }; RaycastPicker.prototype.getObjectsUnderPoint = function (rayPosition, rayDirection) { if (!this._isIntersectingRayInternal(this._asset, rayPosition, rayDirection, true)) return []; //collect pickers this._collectEntities(this._collectedEntities, this._dragNode); //console.log("entities: ", this._entities) var colliders = this._getColliders(); //discard collected pickers this._collectedEntities.length = 0; return colliders; }; RaycastPicker.prototype._collectEntities = function (collectedEntities, dragNode) { if (dragNode === void 0) { dragNode = null; } var picker; for (var i = this._pickers.length - 1; i >= 0; i--) if ((picker = this._pickers[i]).node != dragNode) picker._collectEntities(collectedEntities, dragNode); var node = this._asset; //ensures that raycastPicker entities are always added last, for correct 2D picking var entity; for (var i = this._entities.length - 1; i >= 0; i--) { (entity = this._entities[i]).pickingCollision.rootNode = node; collectedEntities.push(entity); } }; RaycastPicker.prototype.setIgnoreList = function (entities) { this._ignoredEntities = entities; }; RaycastPicker.prototype.isIgnored = function (entity) { if (this._ignoredEntities) { var len = this._ignoredEntities.length; for (var i = 0; i < len; i++) if (this._ignoredEntities[i] == entity) return true; } return false; }; RaycastPicker.sortOnNearT = function (entity1, entity2) { return entity1.pickingCollision.rayEntryDistance > entity2.pickingCollision.rayEntryDistance ? 1 : entity1.pickingCollision.rayEntryDistance < entity2.pickingCollision.rayEntryDistance ? -1 : 0; }; RaycastPicker.prototype._getPickingCollision = function (bestCollision) { if (bestCollision === void 0) { bestCollision = null; } // Sort pickers from closest to furthest to reduce tests. // TODO - test sort filter in JS this._collectedEntities = this._collectedEntities.sort(RaycastPicker.sortOnNearT); // --------------------------------------------------------------------- // Evaluate triangle collisions when needed. // Replaces collision data provided by bounds collider with more precise data. // --------------------------------------------------------------------- var entity; var testCollision; var len = this._collectedEntities.length; for (var i = 0; i < len; i++) { entity = this._collectedEntities[i]; testCollision = entity.pickingCollision; if (bestCollision == null || testCollision.rayEntryDistance < bestCollision.rayEntryDistance) { if ((this._shapeFlag || entity.shapeFlag)) { testCollision.rayEntryDistance = Number.MAX_VALUE; // If a collision exists, update the collision data and stop all checks. if (entity.isIntersectingShape(this.findClosestCollision)) bestCollision = testCollision; } else if (!testCollision.rayOriginIsInsideBounds) { // A bounds collision with no picking collider stops all checks. // Note: a bounds collision with a ray origin inside its bounds is ONLY ever used // to enable the detection of a corresponsding triangle collision. // Therefore, bounds collisions with a ray origin inside its bounds can be ignored // if it has been established that there is NO triangle collider to test bestCollision = testCollision; break; } } else { //if the next rayEntryDistance of testCollision is greater than bestCollision, //there won't be a better collision available break; } } if (bestCollision) RaycastPicker.updatePosition(bestCollision); if (this._dragNode) { if (this._dragNode.container.assetType == '[asset MovieClip]' && this._dragNode.container.adapter) { this._dragNode.container.adapter.setDropTarget(bestCollision ? bestCollision.containerNode : null); } } return bestCollision; }; RaycastPicker.prototype._getColliders = function () { var colliders = []; var pickEntity; var len = this._collectedEntities.length; for (var i = 0; i < len; i++) { pickEntity = this._collectedEntities[i]; pickEntity.pickingCollision.rayEntryDistance = Number.MAX_VALUE; if (pickEntity.isIntersectingShape(false)) colliders.push(pickEntity.node.container); } return colliders; }; RaycastPicker.updatePosition = function (pickingCollision) { var collisionPos = pickingCollision.position || (pickingCollision.position = new Vector3D()); var rayDir = pickingCollision.rayDirection; var rayPos = pickingCollision.rayPosition; var t = pickingCollision.rayEntryDistance; collisionPos.x = rayPos.x + t * rayDir.x; collisionPos.y = rayPos.y + t * rayDir.y; collisionPos.z = rayPos.z + t * rayDir.z; }; RaycastPicker.prototype.dispose = function () { //TODO }; /** * * @param entity */ RaycastPicker.prototype.applyEntity = function (node) { var _a; if (node.container.getEntity()) { var entity = this.pickGroup.abstractions.getAbstraction(node); if (entity._isIntersectingRayInternal(this._rootNode, this._globalRayPosition, this._globalRayDirection)) this._entities.push(entity); } 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(); } }; RaycastPicker._rayPosition = new Vector3D(); RaycastPicker._rayDirection = new Vector3D(); return RaycastPicker; }(AbstractionBase)); export { RaycastPicker };