@awayjs/view
Version:
View for AwayJS
301 lines (300 loc) • 14.1 kB
JavaScript
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 };