/** * Cast ray to find closest intersection with tested mapped objects. * * @method Raycaster.Ray#cast * @memberof Raycaster.Ray * @instance * @since 0.6.0 * * @param {object} [options] - options that may include: * @param {object[]} [options.objects = {Raycaster#mappedObjects}] - Array of game objects to test. If not provided test all mapped game objects. * @param {Phaser.Geom.Point|Point} [options.target] - Ray's target point. Used in other casting methods to determine if ray was targeting mapped objects point. * @param {boolean} [options.internal = false] - Flag determining if method is used by other casting method. * * @return {(Phaser.Geom.Point|boolean)} Ray's closest intersection with tested objects. Returns false if no intersection has been found. Additionally contains reference to hit mapped object and segment if available. */ export function cast(options = {}) { let closestIntersection; let closestSegment; let closestObject; let closestDistance = this.rayRange; let internal = options.internal ? options.internal : false; let startTime = performance.now(); let stats = { method: 'cast', rays: 1, testedMappedObjects: 0, hitMappedObjects: 0, segments: 0, time: 0 }; //if bounding box is defined check bounding box intersection if(this._raycaster && this._raycaster.boundingBox) { let intersections = []; Phaser.Geom.Intersects.GetLineToRectangle(this._ray, this._raycaster.boundingBox.rectangle, intersections); if(intersections.length === 1) closestIntersection = intersections[0]; else if(intersections.length > 1) { for(let intersection of intersections) { let distance = Phaser.Math.Distance.Between(this.origin.x, this.origin.y, intersection.x, intersection.y); if(distance < closestDistance) { closestDistance = distance; closestIntersection = intersection; } } } //if ray target is declared else if(options.target){ let distance = Phaser.Math.Distance.Between(this.origin.x, this.origin.y, options.target.x, options.target.y); //if target is within ray range if(this.rayRange > distance) { closestDistance = distance; closestIntersection = options.target; } } } //if no objects to cast ray were passed, use raycasters mapped objects if(!options.objects) { if(this._raycaster) options.objects = this._raycaster.mappedObjects; else return intersections; } for(let object of options.objects) { let map, boundingBox, boundingBoxIntersections = [], canTestMap = false; if(object.type === 'body' || object.type === 'composite') map = object.raycasterMap; else map = object.data.get('raycasterMap'); stats.testedMappedObjects++; //get slightly enlarged bounding box due to fridge cases, when ray "glanced" border box's corner (v0.10.1) if(internal) { boundingBox = map._boundingBox; } else { boundingBox = map.getBoundingBox(); boundingBox.setTo(boundingBox.x - 0.1, boundingBox.y - 0.1, boundingBox.width + 0.2, boundingBox.height + 0.2); } //check if object is intersected by ray if(Phaser.Geom.Intersects.GetLineToRectangle(this._ray, boundingBox, boundingBoxIntersections).length === 0) continue; //check if bounding box is closer than closest intersection if(Phaser.Geom.Rectangle.ContainsPoint(boundingBox, this.origin)) { canTestMap = true; } else { for(let boundingBoxIntersection of boundingBoxIntersections) { if(Phaser.Math.Distance.Between(this.origin.x, this.origin.y, boundingBoxIntersection.x, boundingBoxIntersection.y) < closestDistance) { canTestMap = true; break; } } } if(!canTestMap) continue; stats.hitMappedObjects++; stats.segments += map.getSegments(this).length; //check intersections for(let segment of map.getSegments(this)) { let intersection = []; //if target point is segmemt point if(options.target) { if( Phaser.Geom.Point.Equals(options.target, segment.getPointA()) || Phaser.Geom.Point.Equals(options.target, segment.getPointB()) ) { intersection = options.target; } else if(!Phaser.Geom.Intersects.LineToLine(this._ray, segment, intersection)) continue; } //if no intersection continue else if(!Phaser.Geom.Intersects.LineToLine(this._ray, segment, intersection)) continue; //get closest intersection let distance = Phaser.Math.Distance.Between(this.origin.x, this.origin.y, intersection.x, intersection.y); if(distance < closestDistance) { closestDistance = distance; closestIntersection = intersection; closestObject = map.object; closestSegment = segment; } } //check if map is circular if(map.circle) { //if circular map has generated points (besides tangent points to ray) if(map._points.length > 0) { continue; } //check if target point is a circle tangent point to ray if(options.target) { let points = map.getPoints(this); let isTangent = false; for(let point of points) { if(Phaser.Geom.Point.Equals(options.target, point)) { //get closest intersection let distance = Phaser.Math.Distance.Between(this.origin.x, this.origin.y, point.x, point.y); if(distance < closestDistance) { closestDistance = distance; closestIntersection = point; closestObject = map.object; isTangent = true; break; } } } if(isTangent) continue; } let circleIntersections = []; let offset = new Phaser.Geom.Point(); offset.x = map.object.x - map.object.displayWidth * (map.object.originX - 0.5); offset.y = map.object.y - map.object.displayHeight * (map.object.originY - 0.5); //calculate circle's center after rotation let rotation = map.object.rotation; if(rotation !== 0) { let vector = new Phaser.Geom.Line(map.object.x, map.object.y, offset.x, offset.y); Phaser.Geom.Line.SetToAngle(vector, map.object.x, map.object.y, Phaser.Geom.Line.Angle(vector) + rotation, Phaser.Geom.Line.Length(vector)); let cB = vector.getPointB(); offset.x = cB.x; offset.y = cB.y; } //create transformed circle let circle = new Phaser.Geom.Circle(offset.x, offset.y, map.object.radius * map.object.scaleX); if(Phaser.Geom.Intersects.GetLineToCircle(this._ray, circle, circleIntersections)) { for(let intersection of circleIntersections) { //get closest intersection let distance = Phaser.Math.Distance.Between(this._ray.x1, this._ray.y1, intersection.x, intersection.y); if(distance < closestDistance) { closestDistance = distance; closestIntersection = intersection; closestObject = map.object; } } } } //check container map's circles if(map.type == 'Container' && map._circles.length > 0) { for(let circle of map._circles) { //check if target point is a circle tangent point to ray if(options.target) { let isTangent = false; for(let point of circle.points) { if(Phaser.Geom.Point.Equals(options.target, point)) { //get closest intersection let distance = Phaser.Math.Distance.Between(this.origin.x, this.origin.y, point.x, point.y); if(distance < closestDistance) { closestDistance = distance; closestIntersection = point; closestObject = map.object; isTangent = true; break; } } } if(isTangent) continue; } let circleIntersections = []; if(Phaser.Geom.Intersects.GetLineToCircle(this._ray, circle, circleIntersections)) { for(let intersection of circleIntersections) { //get closest intersection let distance = Phaser.Math.Distance.Between(this._ray.x1, this._ray.y1, intersection.x, intersection.y); if(distance < closestDistance) { closestDistance = distance; closestIntersection = intersection; closestObject = map.object; } } } } } } //update stats if(internal) { this._stats.rays++; this._stats.testedMappedObjects += stats.testedMappedObjects; this._stats.hitMappedObjects += stats.hitMappedObjects; this._stats.segments += stats.segments; } else { stats.time = performance.now() - startTime; this._stats = stats; } let result; if(!closestIntersection) { if(this.ignoreNotIntersectedRays) return false; result = this._ray.getPointB(); } else { result = new Phaser.Geom.Point(closestIntersection.x, closestIntersection.y); result.segment = closestSegment; result.object = closestObject; } if(this.round) { result.x = Math.round(result.x); result.y = Math.round(result.y); } if(!internal) this.drawDebug([result]); return result; }