新增visiblityComputer 表示从给定的一组遮挡物的原点可以看到哪些区域

This commit is contained in:
yhh
2020-12-07 21:01:21 +08:00
parent 06b2163aaa
commit 8c5e4537a6
8 changed files with 1378 additions and 1 deletions

View File

@@ -1789,6 +1789,7 @@ declare module es {
*/ */
static clamp01(value: number): number; static clamp01(value: number): number;
static angleBetweenVectors(from: Vector2, to: Vector2): number; static angleBetweenVectors(from: Vector2, to: Vector2): number;
static angleToVector(angleRadians: number, length: number): Vector2;
/** /**
* 增加t并确保它总是大于或等于0并且小于长度 * 增加t并确保它总是大于或等于0并且小于长度
* @param t * @param t
@@ -3222,6 +3223,35 @@ declare module es {
equals(other: T): boolean; equals(other: T): boolean;
} }
} }
declare module es {
class Node<T> {
element: T;
next: Node<T>;
constructor(element: T, next?: Node<T>);
}
interface equalsFnType<T> {
(a: T, b: T): boolean;
}
function defaultEquals<T>(a: T, b: T): boolean;
class LinkedList<T> {
protected count: number;
protected next: any;
protected equalsFn: equalsFnType<T>;
protected head: Node<T>;
constructor(equalsFn?: typeof defaultEquals);
push(element: T): void;
removeAt(index: number): T;
getElementAt(index: number): Node<T>;
insert(element: T, index: number): boolean;
indexOf(element: T): number;
remove(element: T): void;
clear(): void;
size(): number;
isEmpty(): boolean;
getHead(): Node<T>;
toString(): string;
}
}
declare module es { declare module es {
/** /**
* 可以用于列表池的简单类 * 可以用于列表池的简单类
@@ -4032,6 +4062,156 @@ declare module linq {
thenByDescending(keySelector: (key: T) => any): List<T>; thenByDescending(keySelector: (key: T) => any): List<T>;
} }
} }
declare module es {
/**
* 一段的终点
*/
class EndPoint {
/** 该部分的位置 */
position: Vector2;
/** 如果这个端点是一个段的起始点或终点(每个段只有一个起始点和一个终点) */
begin: boolean;
/** 该端点所属的段 */
segment: Segment;
/** 端点相对于能见度测试位置的角度 */
angle: number;
constructor();
}
class EndPointComparer implements IComparer<EndPoint> {
/**
* 按角度对点进行排序的比较功能
* @param a
* @param b
*/
compare(a: EndPoint, b: EndPoint): 1 | 0 | -1;
}
}
declare module es {
/**
* 表示可见性网格中的遮挡线段
*/
class Segment {
/**
* 该部分的第一个终点
*/
p1: EndPoint;
/**
* 该部分的第二个终点
*/
p2: EndPoint;
constructor();
}
}
declare module es {
/**
* 类,它可以计算出一个网格,表示从给定的一组遮挡物的原点可以看到哪些区域。使用方法如下。
*
* - 调用 begin
* - 添加任何遮挡物
* - 调用end来获取可见度多边形。当调用end时所有的内部存储都会被清空。
*/
class VisibilityComputer {
/**
* 在近似圆的时候要用到的线的总数。只需要一个180度的半球所以这将是近似该半球的线段数
*/
lineCountForCircleApproximation: number;
_radius: number;
_origin: Vector2;
_isSpotLight: boolean;
_spotStartAngle: number;
_spotEndAngle: number;
_endPoints: EndPoint[];
_segments: Segment[];
_radialComparer: EndPointComparer;
static _cornerCache: Vector2[];
static _openSegments: LinkedList<Segment>;
constructor(origin?: Vector2, radius?: number);
/**
* 增加了一个对撞机作为PolyLight的遮蔽器
* @param collider
*/
addColliderOccluder(collider: Collider): void;
/**
* 增加了一个圆形的遮挡器
* @param position
* @param radius
*/
addCircleOccluder(position: Vector2, radius: number): void;
/**
* 增加一个线型遮挡器
* @param p1
* @param p2
*/
addLineOccluder(p1: Vector2, p2: Vector2): void;
/**
* 增加一个方形的遮挡器
* @param bounds
*/
addSquareOccluder(bounds: Rectangle): void;
/**
* 添加一个段,第一个点在可视化中显示,但第二个点不显示。
* 每个端点都是两个段的一部分,但我们希望只显示一次
* @param p1
* @param p2
*/
addSegment(p1: Vector2, p2: Vector2): void;
/**
* 移除所有的遮挡物
*/
clearOccluders(): void;
/**
* 为计算机计算当前的聚光做好准备
* @param origin
* @param radius
*/
begin(origin: Vector2, radius: number): void;
/**
* 计算可见性多边形并返回三角形扇形的顶点减去中心顶点。返回的数组来自ListPool
*/
end(): Vector2[];
addTriangle(triangles: Vector2[], angle1: number, angle2: number, segment: Segment): void;
/**
* 计算直线p1-p2与p3-p4的交点
* @param p1
* @param p2
* @param p3
* @param p4
*/
static lineLineIntersection(p1: Vector2, p2: Vector2, p3: Vector2, p4: Vector2): Vector2;
static between(value: number, min: number, max: number): boolean;
/**
* 辅助函数,用于沿外周线构建分段,以限制光的半径。
*/
loadRectangleBoundaries(): void;
/**
* 助手我们知道a段在b的前面吗实现不反对称也就是说isSegmentInFrontOf(a, b) != (!isSegmentInFrontOf(b, a)))。
* 另外要注意的是,在可见性算法中,它只需要在有限的一组情况下工作,我不认为它能处理所有的情况。
* 见http://www.redblobgames.com/articles/visibility/segment-sorting.html
* @param a
* @param b
* @param relativeTo
*/
isSegmentInFrontOf(a: Segment, b: Segment, relativeTo: Vector2): boolean;
/**
* 返回略微缩短的向量p * (1 - f) + q * f
* @param p
* @param q
* @param f
*/
static interpolate(p: Vector2, q: Vector2, f: number): Vector2;
/**
* 返回点是否在直线p1-p2的 "左边"。
* @param p1
* @param p2
* @param point
*/
static isLeftOf(p1: Vector2, p2: Vector2, point: Vector2): boolean;
/**
* 处理片段,以便我们稍后对它们进行分类
*/
updateSegments(): void;
}
}
declare module es { declare module es {
interface ITimer { interface ITimer {
context: any; context: any;

View File

@@ -4453,6 +4453,9 @@ var es;
MathHelper.angleBetweenVectors = function (from, to) { MathHelper.angleBetweenVectors = function (from, to) {
return Math.atan2(to.y - from.y, to.x - from.x); return Math.atan2(to.y - from.y, to.x - from.x);
}; };
MathHelper.angleToVector = function (angleRadians, length) {
return new es.Vector2(Math.cos(angleRadians) * length, Math.sin(angleRadians) * length);
};
/** /**
* 增加t并确保它总是大于或等于0并且小于长度 * 增加t并确保它总是大于或等于0并且小于长度
* @param t * @param t
@@ -8069,6 +8072,190 @@ var es;
es.GlobalManager = GlobalManager; es.GlobalManager = GlobalManager;
})(es || (es = {})); })(es || (es = {}));
var es; var es;
(function (es) {
var Node = /** @class */ (function () {
// next为可选参数如果不传则为undefined
function Node(element, next) {
this.element = element;
this.next = next;
}
return Node;
}());
es.Node = Node;
function defaultEquals(a, b) {
return a === b;
}
es.defaultEquals = defaultEquals;
var LinkedList = /** @class */ (function () {
function LinkedList(equalsFn) {
if (equalsFn === void 0) { equalsFn = defaultEquals; }
// 初始化链表内部变量
this.count = 0;
this.next = undefined;
this.equalsFn = equalsFn;
this.head = null;
}
// 链表尾部添加元素
LinkedList.prototype.push = function (element) {
// 声明结点变量,将元素当作参数传入生成结点
var node = new Node(element);
// 存储遍历到的链表元素
var current;
if (this.head == null) {
// 链表为空,直接将链表头部赋值为结点变量
this.head = node;
}
else {
// 链表不为空,我们只能拿到链表中第一个元素的引用
current = this.head;
// 循环访问链表
while (current.next != null) {
// 赋值遍历到的元素
current = current.next;
}
// 此时已经得到了链表的最后一个元素(null),将链表的下一个元素赋值为结点变量。
current.next = node;
}
// 链表长度自增
this.count++;
};
// 移除链表指定位置的元素
LinkedList.prototype.removeAt = function (index) {
// 边界判断: 参数是否有效
if (index >= 0 && index < this.count) {
// 获取当前链表头部元素
var current = this.head;
// 移除第一项
if (index === 0) {
this.head = current.next;
}
else {
// 获取目标参数上一个结点
var previous = this.getElementAt(index - 1);
// 当前结点指向目标结点
current = previous.next;
/**
* 目标结点元素已找到
* previous.next指向目标结点
* current.next指向undefined
* previous.next指向current.next即删除目标结点的元素
*/
previous.next = current.next;
}
// 链表长度自减
this.count--;
// 返回当前删除的目标结点
return current.element;
}
return undefined;
};
// 获取链表指定位置的结点
LinkedList.prototype.getElementAt = function (index) {
// 参数校验
if (index >= 0 && index <= this.count) {
// 获取链表头部元素
var current = this.head;
// 从链表头部遍历至目标结点位置
for (var i = 0; i < index && current != null; i++) {
// 当前结点指向下一个目标结点
current = current.next;
}
// 返回目标结点数据
return current;
}
return undefined;
};
// 向链表中插入元素
LinkedList.prototype.insert = function (element, index) {
// 参数有效性判断
if (index >= 0 && index <= this.count) {
// 声明节点变量,将当前要插入的元素作为参数生成结点
var node = new Node(element);
// 第一个位置添加元素
if (index === 0) {
// 将节点变量(node)的下一个元素指向链表的头部元素
node.next = this.head;
// 链表头部元素赋值为节点变量
this.head = node;
}
else {
// 获取目标结点的上一个结点
var previous = this.getElementAt(index - 1);
// 将节点变量的下一个元素指向目标节点
node.next = previous.next;
/**
* 此时node中当前结点为要插入的值
* next为原位置处的结点
* 因此将当前结点赋值为node就完成了结点插入操作
*/
previous.next = node;
}
// 链表长度自增
this.count++;
return true;
}
return false;
};
// 根据元素获取其在链表中的索引
LinkedList.prototype.indexOf = function (element) {
// 获取链表顶部元素
var current = this.head;
// 遍历链表内的元素
for (var i = 0; i < this.count && current != null; i++) {
// 判断当前链表中的结点与目标结点是否相等
if (this.equalsFn(element, current.element)) {
// 返回索引
return i;
}
// 当前结点指向下一个结点
current = current.next;
}
// 目标元素不存在
return -1;
};
// 移除链表中的指定元素
LinkedList.prototype.remove = function (element) {
// 获取element的索引,移除索引位置的元素
this.removeAt(this.indexOf(element));
};
LinkedList.prototype.clear = function () {
this.head = undefined;
this.count = 0;
};
// 获取链表长度
LinkedList.prototype.size = function () {
return this.count;
};
// 判断链表是否为空
LinkedList.prototype.isEmpty = function () {
return this.size() === 0;
};
// 获取链表头部元素
LinkedList.prototype.getHead = function () {
return this.head;
};
// 获取链表中的所有元素
LinkedList.prototype.toString = function () {
if (this.head == null) {
return "";
}
var objString = "" + this.head.element;
// 获取链表顶点的下一个结点
var current = this.head.next;
// 遍历链表中的所有结点
for (var i = 1; i < this.size() && current != null; i++) {
// 将当前结点的元素拼接到最终要生成的字符串对象中
objString = objString + ", " + current.element;
// 当前结点指向链表的下一个元素
current = current.next;
}
return objString;
};
return LinkedList;
}());
es.LinkedList = LinkedList;
})(es || (es = {}));
var es;
(function (es) { (function (es) {
/** /**
* 可以用于列表池的简单类 * 可以用于列表池的简单类
@@ -9645,6 +9832,388 @@ var linq;
linq.OrderedList = OrderedList; linq.OrderedList = OrderedList;
})(linq || (linq = {})); })(linq || (linq = {}));
var es; var es;
(function (es) {
/**
* 一段的终点
*/
var EndPoint = /** @class */ (function () {
function EndPoint() {
this.position = es.Vector2.zero;
this.begin = false;
this.segment = null;
this.angle = 0;
}
return EndPoint;
}());
es.EndPoint = EndPoint;
var EndPointComparer = /** @class */ (function () {
function EndPointComparer() {
}
/**
* 按角度对点进行排序的比较功能
* @param a
* @param b
*/
EndPointComparer.prototype.compare = function (a, b) {
// 按角度顺序移动
if (a.angle > b.angle)
return 1;
if (a.angle < b.angle)
return -1;
// 但对于纽带我们希望Begin节点在End节点之前
if (!a.begin && b.begin)
return 1;
if (a.begin && !b.begin)
return -1;
return 0;
};
return EndPointComparer;
}());
es.EndPointComparer = EndPointComparer;
})(es || (es = {}));
var es;
(function (es) {
/**
* 表示可见性网格中的遮挡线段
*/
var Segment = /** @class */ (function () {
function Segment() {
this.p1 = null;
this.p2 = null;
}
return Segment;
}());
es.Segment = Segment;
})(es || (es = {}));
///<reference path="../LinkList.ts" />
var es;
///<reference path="../LinkList.ts" />
(function (es) {
/**
* 类,它可以计算出一个网格,表示从给定的一组遮挡物的原点可以看到哪些区域。使用方法如下。
*
* - 调用 begin
* - 添加任何遮挡物
* - 调用end来获取可见度多边形。当调用end时所有的内部存储都会被清空。
*/
var VisibilityComputer = /** @class */ (function () {
function VisibilityComputer(origin, radius) {
/**
* 在近似圆的时候要用到的线的总数。只需要一个180度的半球所以这将是近似该半球的线段数
*/
this.lineCountForCircleApproximation = 10;
this._radius = 0;
this._origin = es.Vector2.zero;
this._isSpotLight = false;
this._spotStartAngle = 0;
this._spotEndAngle = 0;
this._endPoints = [];
this._segments = [];
this._origin = origin;
this._radius = radius;
this._radialComparer = new es.EndPointComparer();
}
/**
* 增加了一个对撞机作为PolyLight的遮蔽器
* @param collider
*/
VisibilityComputer.prototype.addColliderOccluder = function (collider) {
// 特殊情况下BoxColliders没有旋转
if (collider instanceof es.BoxCollider && collider.rotation == 0) {
this.addSquareOccluder(collider.bounds);
return;
}
if (collider instanceof es.PolygonCollider) {
var poly = collider.shape;
for (var i = 0; i < poly.points.length; i++) {
var firstIndex = i - 1;
if (i == 0)
firstIndex += poly.points.length;
this.addLineOccluder(es.Vector2.add(poly.points[firstIndex], poly.position), es.Vector2.add(poly.points[i], poly.position));
}
}
else if (collider instanceof es.CircleCollider) {
this.addCircleOccluder(collider.absolutePosition, collider.radius);
}
};
/**
* 增加了一个圆形的遮挡器
* @param position
* @param radius
*/
VisibilityComputer.prototype.addCircleOccluder = function (position, radius) {
var dirToCircle = es.Vector2.subtract(position, this._origin);
var angle = Math.atan2(dirToCircle.y, dirToCircle.x);
var stepSize = Math.PI / this.lineCountForCircleApproximation;
var startAngle = angle + es.MathHelper.PiOver2;
var lastPt = es.MathHelper.angleToVector(startAngle, radius).add(position);
for (var i = 1; i < this.lineCountForCircleApproximation; i++) {
var nextPt = es.MathHelper.angleToVector(startAngle + i * stepSize, radius).add(position);
this.addLineOccluder(lastPt, nextPt);
lastPt = nextPt;
}
};
/**
* 增加一个线型遮挡器
* @param p1
* @param p2
*/
VisibilityComputer.prototype.addLineOccluder = function (p1, p2) {
this.addSegment(p1, p2);
};
/**
* 增加一个方形的遮挡器
* @param bounds
*/
VisibilityComputer.prototype.addSquareOccluder = function (bounds) {
var tr = new es.Vector2(bounds.right, bounds.top);
var bl = new es.Vector2(bounds.left, bounds.bottom);
var br = new es.Vector2(bounds.right, bounds.bottom);
this.addSegment(bounds.location, tr);
this.addSegment(tr, br);
this.addSegment(br, bl);
this.addSegment(bl, bounds.location);
};
/**
* 添加一个段,第一个点在可视化中显示,但第二个点不显示。
* 每个端点都是两个段的一部分,但我们希望只显示一次
* @param p1
* @param p2
*/
VisibilityComputer.prototype.addSegment = function (p1, p2) {
var segment = new es.Segment();
var endPoint1 = new es.EndPoint();
var endPoint2 = new es.EndPoint();
endPoint1.position = p1;
endPoint1.segment = segment;
endPoint2.position = p2;
endPoint2.segment = segment;
segment.p1 = endPoint1;
segment.p2 = endPoint2;
this._segments.push(segment);
this._endPoints.push(endPoint1);
this._endPoints.push(endPoint2);
};
/**
* 移除所有的遮挡物
*/
VisibilityComputer.prototype.clearOccluders = function () {
this._segments.length = 0;
this._endPoints.length = 0;
};
/**
* 为计算机计算当前的聚光做好准备
* @param origin
* @param radius
*/
VisibilityComputer.prototype.begin = function (origin, radius) {
this._origin = origin;
this._radius = radius;
this._isSpotLight = false;
};
/**
* 计算可见性多边形并返回三角形扇形的顶点减去中心顶点。返回的数组来自ListPool
*/
VisibilityComputer.prototype.end = function () {
var e_6, _a;
var output = es.ListPool.obtain();
this.updateSegments();
this._endPoints.sort(this._radialComparer.compare);
var currentAngle = 0;
// 在扫描开始时,我们想知道哪些段是活动的。
// 最简单的方法是先进行一次段的收集,然后再进行一次段的收集和处理。
// 然而,更有效的方法是通过所有的段,找出哪些段与最初的扫描线相交,然后对它们进行分类
for (var pass = 0; pass < 2; pass++) {
try {
for (var _b = __values(this._endPoints), _c = _b.next(); !_c.done; _c = _b.next()) {
var p = _c.value;
var currentOld = VisibilityComputer._openSegments.size() == 0 ? null : VisibilityComputer._openSegments.getHead().element;
if (p.begin) {
// 在列表中的正确位置插入
var node = VisibilityComputer._openSegments.getHead();
while (node != null && this.isSegmentInFrontOf(p.segment, node.element, this._origin))
node = node.next;
if (node == null)
VisibilityComputer._openSegments.push(p.segment);
else
VisibilityComputer._openSegments.insert(p.segment, VisibilityComputer._openSegments.indexOf(node.element));
}
else {
VisibilityComputer._openSegments.remove(p.segment);
}
var currentNew = null;
if (VisibilityComputer._openSegments.size() != 0)
currentNew = VisibilityComputer._openSegments.getHead().element;
if (currentOld != currentNew) {
if (pass == 1) {
if (!this._isSpotLight || (VisibilityComputer.between(currentAngle, this._spotStartAngle, this._spotEndAngle) &&
VisibilityComputer.between(p.angle, this._spotStartAngle, this._spotEndAngle)))
this.addTriangle(output, currentAngle, p.angle, currentOld);
}
currentAngle = p.angle;
}
}
}
catch (e_6_1) { e_6 = { error: e_6_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_6) throw e_6.error; }
}
}
VisibilityComputer._openSegments.clear();
this.clearOccluders();
return output;
};
VisibilityComputer.prototype.addTriangle = function (triangles, angle1, angle2, segment) {
var p1 = this._origin.clone();
var p2 = new es.Vector2(this._origin.x + Math.cos(angle1), this._origin.y + Math.sin(angle1));
var p3 = es.Vector2.zero;
var p4 = es.Vector2.zero;
if (segment != null) {
// 将三角形停在相交线段上
p3.x = segment.p1.position.x;
p3.y = segment.p1.position.y;
p4.x = segment.p2.position.x;
p4.y = segment.p2.position.y;
}
else {
p3.x = this._origin.x + Math.cos(angle1) * this._radius * 2;
p3.y = this._origin.y + Math.sin(angle1) * this._radius * 2;
p4.x = this._origin.x + Math.cos(angle2) * this._radius * 2;
p4.y = this._origin.y + Math.sin(angle2) * this._radius * 2;
}
var pBegin = VisibilityComputer.lineLineIntersection(p3, p4, p1, p2);
p2.x = this._origin.x + Math.cos(angle2);
p2.y = this._origin.y + Math.sin(angle2);
var pEnd = VisibilityComputer.lineLineIntersection(p3, p4, p1, p2);
triangles.push(pBegin);
triangles.push(pEnd);
};
/**
* 计算直线p1-p2与p3-p4的交点
* @param p1
* @param p2
* @param p3
* @param p4
*/
VisibilityComputer.lineLineIntersection = function (p1, p2, p3, p4) {
var s = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x))
/ ((p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y));
return new es.Vector2(p1.x + s * (p2.x - p1.x), p1.y + s * (p2.y - p1.y));
};
VisibilityComputer.between = function (value, min, max) {
value = (360 + (value % 360)) % 360;
min = (3600000 + min) % 360;
max = (3600000 + max) % 360;
if (min < max)
return min <= value && value <= max;
return min <= value || value <= max;
};
/**
* 辅助函数,用于沿外周线构建分段,以限制光的半径。
*/
VisibilityComputer.prototype.loadRectangleBoundaries = function () {
this.addSegment(new es.Vector2(this._origin.x - this._radius, this._origin.y - this._radius), new es.Vector2(this._origin.x + this._radius, this._origin.y - this._radius));
this.addSegment(new es.Vector2(this._origin.x - this._radius, this._origin.y + this._radius), new es.Vector2(this._origin.x + this._radius, this._origin.y + this._radius));
this.addSegment(new es.Vector2(this._origin.x - this._radius, this._origin.y - this._radius), new es.Vector2(this._origin.x - this._radius, this._origin.y + this._radius));
this.addSegment(new es.Vector2(this._origin.x + this._radius, this._origin.y - this._radius), new es.Vector2(this._origin.x + this._radius, this._origin.y + this._radius));
};
/**
* 助手我们知道a段在b的前面吗实现不反对称也就是说isSegmentInFrontOf(a, b) != (!isSegmentInFrontOf(b, a)))。
* 另外要注意的是,在可见性算法中,它只需要在有限的一组情况下工作,我不认为它能处理所有的情况。
* 见http://www.redblobgames.com/articles/visibility/segment-sorting.html
* @param a
* @param b
* @param relativeTo
*/
VisibilityComputer.prototype.isSegmentInFrontOf = function (a, b, relativeTo) {
// 注意:我们稍微缩短了段,所以在这个算法中,端点的交点(共同)不计入交点。
var a1 = VisibilityComputer.isLeftOf(a.p2.position, a.p1.position, VisibilityComputer.interpolate(b.p1.position, b.p2.position, 0.01));
var a2 = VisibilityComputer.isLeftOf(a.p2.position, a.p1.position, VisibilityComputer.interpolate(b.p2.position, b.p1.position, 0.01));
var a3 = VisibilityComputer.isLeftOf(a.p2.position, a.p1.position, relativeTo);
var b1 = VisibilityComputer.isLeftOf(b.p2.position, b.p1.position, VisibilityComputer.interpolate(a.p1.position, a.p2.position, 0.01));
var b2 = VisibilityComputer.isLeftOf(b.p2.position, b.p1.position, VisibilityComputer.interpolate(a.p2.position, a.p1.position, 0.01));
var b3 = VisibilityComputer.isLeftOf(b.p2.position, b.p1.position, relativeTo);
// 注考虑A1-A2这条线。如果B1和B2都在一条边上而relativeTo在另一条边上那么A就在观看者和B之间。
if (b1 == b2 && b2 != b3)
return true;
if (a1 == a2 && a2 == a3)
return true;
if (a1 == a2 && a2 != a3)
return false;
if (b1 == b2 && b2 == b3)
return false;
// 如果A1 !=A2B1 !=B2那么我们就有一个交点。
// 一个更稳健的实现是在交叉点上分割段,使一部分段在前面,一部分段在后面,但无论如何我们不应该有重叠的碰撞器,所以这不是太重要
return false;
// 注意以前的实现方式是a.d < b.d.,这比较简单,但当段的大小不一样时,就麻烦了。
// 如果你是在一个网格上,而且段的大小相似,那么使用距离将是一个更简单和更快的实现。
};
/**
* 返回略微缩短的向量p * (1 - f) + q * f
* @param p
* @param q
* @param f
*/
VisibilityComputer.interpolate = function (p, q, f) {
return new es.Vector2(p.x * (1 - f) + q.x * f, p.y * (1 - f) + q.y * f);
};
/**
* 返回点是否在直线p1-p2的 "左边"。
* @param p1
* @param p2
* @param point
*/
VisibilityComputer.isLeftOf = function (p1, p2, point) {
var cross = (p2.x - p1.x) * (point.y - p1.y)
- (p2.y - p1.y) * (point.x - p1.x);
return cross < 0;
};
/**
* 处理片段,以便我们稍后对它们进行分类
*/
VisibilityComputer.prototype.updateSegments = function () {
var e_7, _a;
try {
for (var _b = __values(this._segments), _c = _b.next(); !_c.done; _c = _b.next()) {
var segment = _c.value;
// 注未来的优化我们可以记录象限和y/x或x/y比率并按象限、比率排序而不是调用atan2。
// 参见<https://github.com/mikolalysenko/compare-slope>,有一个库可以做到这一点
segment.p1.angle = Math.atan2(segment.p1.position.y - this._origin.y, segment.p1.position.x - this._origin.x);
segment.p2.angle = Math.atan2(segment.p2.position.y - this._origin.y, segment.p2.position.x - this._origin.x);
// Pi和Pi之间的映射角度
var dAngle = segment.p2.angle - segment.p1.angle;
if (dAngle <= -Math.PI)
dAngle += Math.PI * 2;
if (dAngle > Math.PI)
dAngle -= Math.PI * 2;
segment.p1.begin = (dAngle > 0);
segment.p2.begin = !segment.p1.begin;
}
}
catch (e_7_1) { e_7 = { error: e_7_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_7) throw e_7.error; }
}
// 如果我们有一个聚光灯,我们需要存储前两个段的角度。
// 这些是光斑的边界,我们将用它们来过滤它们之外的任何顶点。
if (this._isSpotLight) {
this._spotStartAngle = this._segments[0].p2.angle;
this._spotEndAngle = this._segments[1].p2.angle;
}
};
VisibilityComputer._cornerCache = [];
VisibilityComputer._openSegments = new es.LinkedList();
return VisibilityComputer;
}());
es.VisibilityComputer = VisibilityComputer;
})(es || (es = {}));
var es;
(function (es) { (function (es) {
/** /**
* 私有类隐藏ITimer的实现 * 私有类隐藏ITimer的实现

File diff suppressed because one or more lines are too long

View File

@@ -88,6 +88,10 @@ module es {
return Math.atan2(to.y - from.y, to.x - from.x); return Math.atan2(to.y - from.y, to.x - from.x);
} }
public static angleToVector(angleRadians: number, length: number){
return new Vector2(Math.cos(angleRadians) * length, Math.sin(angleRadians) * length);
}
/** /**
* 增加t并确保它总是大于或等于0并且小于长度 * 增加t并确保它总是大于或等于0并且小于长度
* @param t * @param t

View File

@@ -0,0 +1,200 @@
module es {
export class Node<T>{
element: T;
next: Node<T>;
// next为可选参数如果不传则为undefined
constructor(element: T, next?: Node<T>) {
this.element = element;
this.next = next;
}
}
// 定义验证函数要传的参数和返回结果
interface equalsFnType<T> {
(a: T, b: T): boolean;
}
export function defaultEquals<T>(a: T, b: T): boolean {
return a === b;
}
export class LinkedList<T> {
// 声明链表内需要的变量并定义其类型
protected count: number;
protected next: any;
protected equalsFn: equalsFnType<T>;
protected head: Node<T>;
constructor(equalsFn = defaultEquals) {
// 初始化链表内部变量
this.count = 0;
this.next = undefined;
this.equalsFn = equalsFn;
this.head = null;
}
// 链表尾部添加元素
push(element: T) {
// 声明结点变量,将元素当作参数传入生成结点
const node = new Node(element);
// 存储遍历到的链表元素
let current;
if (this.head == null) {
// 链表为空,直接将链表头部赋值为结点变量
this.head = node;
} else {
// 链表不为空,我们只能拿到链表中第一个元素的引用
current = this.head;
// 循环访问链表
while (current.next != null) {
// 赋值遍历到的元素
current = current.next;
}
// 此时已经得到了链表的最后一个元素(null),将链表的下一个元素赋值为结点变量。
current.next = node;
}
// 链表长度自增
this.count++;
}
// 移除链表指定位置的元素
removeAt(index: number) {
// 边界判断: 参数是否有效
if (index >= 0 && index < this.count) {
// 获取当前链表头部元素
let current = this.head;
// 移除第一项
if (index === 0) {
this.head = current.next;
} else {
// 获取目标参数上一个结点
const previous = this.getElementAt(index - 1);
// 当前结点指向目标结点
current = previous.next;
/**
* 目标结点元素已找到
* previous.next指向目标结点
* current.next指向undefined
* previous.next指向current.next即删除目标结点的元素
*/
previous.next = current.next;
}
// 链表长度自减
this.count--;
// 返回当前删除的目标结点
return current.element;
}
return undefined;
}
// 获取链表指定位置的结点
getElementAt(index: number) {
// 参数校验
if (index >= 0 && index <= this.count) {
// 获取链表头部元素
let current = this.head;
// 从链表头部遍历至目标结点位置
for (let i = 0; i < index && current != null; i++) {
// 当前结点指向下一个目标结点
current = current.next;
}
// 返回目标结点数据
return current;
}
return undefined;
}
// 向链表中插入元素
insert(element: T, index: number) {
// 参数有效性判断
if (index >= 0 && index <= this.count) {
// 声明节点变量,将当前要插入的元素作为参数生成结点
const node = new Node(element);
// 第一个位置添加元素
if (index === 0) {
// 将节点变量(node)的下一个元素指向链表的头部元素
node.next = this.head;
// 链表头部元素赋值为节点变量
this.head = node;
} else {
// 获取目标结点的上一个结点
const previous = this.getElementAt(index - 1);
// 将节点变量的下一个元素指向目标节点
node.next = previous.next;
/**
* 此时node中当前结点为要插入的值
* next为原位置处的结点
* 因此将当前结点赋值为node就完成了结点插入操作
*/
previous.next = node;
}
// 链表长度自增
this.count++;
return true;
}
return false;
}
// 根据元素获取其在链表中的索引
indexOf(element: T) {
// 获取链表顶部元素
let current = this.head;
// 遍历链表内的元素
for (let i = 0; i < this.count && current != null; i++) {
// 判断当前链表中的结点与目标结点是否相等
if (this.equalsFn(element, current.element)) {
// 返回索引
return i;
}
// 当前结点指向下一个结点
current = current.next;
}
// 目标元素不存在
return -1;
}
// 移除链表中的指定元素
remove(element: T) {
// 获取element的索引,移除索引位置的元素
this.removeAt(this.indexOf(element));
}
clear() {
this.head = undefined;
this.count = 0;
}
// 获取链表长度
size() {
return this.count;
}
// 判断链表是否为空
isEmpty() {
return this.size() === 0;
}
// 获取链表头部元素
getHead() {
return this.head;
}
// 获取链表中的所有元素
toString() {
if (this.head == null) {
return "";
}
let objString = `${this.head.element}`;
// 获取链表顶点的下一个结点
let current = this.head.next;
// 遍历链表中的所有结点
for (let i = 1; i < this.size() && current != null; i++) {
// 将当前结点的元素拼接到最终要生成的字符串对象中
objString = `${objString}, ${current.element}`;
// 当前结点指向链表的下一个元素
current = current.next;
}
return objString;
}
}
}

View File

@@ -0,0 +1,47 @@
module es {
/**
* 一段的终点
*/
export class EndPoint {
/** 该部分的位置 */
public position: Vector2;
/** 如果这个端点是一个段的起始点或终点(每个段只有一个起始点和一个终点) */
public begin: boolean;
/** 该端点所属的段 */
public segment: Segment;
/** 端点相对于能见度测试位置的角度 */
public angle: number;
constructor() {
this.position = Vector2.zero;
this.begin = false;
this.segment = null;
this.angle = 0;
}
}
export class EndPointComparer implements IComparer<EndPoint> {
/**
* 按角度对点进行排序的比较功能
* @param a
* @param b
*/
public compare(a: EndPoint, b: EndPoint) {
// 按角度顺序移动
if (a.angle > b.angle)
return 1;
if (a.angle < b.angle)
return -1;
// 但对于纽带我们希望Begin节点在End节点之前
if (!a.begin && b.begin)
return 1;
if (a.begin && !b.begin)
return -1;
return 0;
}
}
}

View File

@@ -0,0 +1,20 @@
module es {
/**
* 表示可见性网格中的遮挡线段
*/
export class Segment {
/**
* 该部分的第一个终点
*/
public p1: EndPoint;
/**
* 该部分的第二个终点
*/
public p2: EndPoint;
constructor(){
this.p1 = null;
this.p2 = null;
}
}
}

View File

@@ -0,0 +1,357 @@
///<reference path="../LinkList.ts" />
module es {
/**
* 类,它可以计算出一个网格,表示从给定的一组遮挡物的原点可以看到哪些区域。使用方法如下。
*
* - 调用 begin
* - 添加任何遮挡物
* - 调用end来获取可见度多边形。当调用end时所有的内部存储都会被清空。
*/
export class VisibilityComputer {
/**
* 在近似圆的时候要用到的线的总数。只需要一个180度的半球所以这将是近似该半球的线段数
*/
public lineCountForCircleApproximation: number = 10;
public _radius: number = 0;
public _origin: Vector2 = Vector2.zero;
public _isSpotLight: boolean = false;
public _spotStartAngle: number = 0;
public _spotEndAngle: number = 0;
public _endPoints: EndPoint[] = [];
public _segments: Segment[] = [];
public _radialComparer: EndPointComparer;
public static _cornerCache: Vector2[] = [];
public static _openSegments: LinkedList<Segment> = new LinkedList<Segment>();
constructor(origin?: Vector2, radius?: number){
this._origin = origin;
this._radius = radius;
this._radialComparer = new EndPointComparer();
}
/**
* 增加了一个对撞机作为PolyLight的遮蔽器
* @param collider
*/
public addColliderOccluder(collider: Collider) {
// 特殊情况下BoxColliders没有旋转
if (collider instanceof BoxCollider && collider.rotation == 0) {
this.addSquareOccluder(collider.bounds);
return;
}
if (collider instanceof PolygonCollider) {
let poly = collider.shape as Polygon;
for (let i = 0; i < poly.points.length; i ++) {
let firstIndex = i - 1;
if (i == 0)
firstIndex += poly.points.length;
this.addLineOccluder(Vector2.add(poly.points[firstIndex], poly.position),
Vector2.add(poly.points[i], poly.position));
}
} else if(collider instanceof CircleCollider) {
this.addCircleOccluder(collider.absolutePosition, collider.radius);
}
}
/**
* 增加了一个圆形的遮挡器
* @param position
* @param radius
*/
public addCircleOccluder(position: Vector2, radius: number){
let dirToCircle = Vector2.subtract(position, this._origin);
let angle = Math.atan2(dirToCircle.y, dirToCircle.x);
let stepSize = Math.PI / this.lineCountForCircleApproximation;
let startAngle = angle + MathHelper.PiOver2;
let lastPt = MathHelper.angleToVector(startAngle, radius).add(position);
for (let i = 1; i < this.lineCountForCircleApproximation; i ++) {
let nextPt = MathHelper.angleToVector(startAngle + i * stepSize, radius).add(position);
this.addLineOccluder(lastPt, nextPt);
lastPt = nextPt;
}
}
/**
* 增加一个线型遮挡器
* @param p1
* @param p2
*/
public addLineOccluder(p1: Vector2, p2: Vector2) {
this.addSegment(p1, p2);
}
/**
* 增加一个方形的遮挡器
* @param bounds
*/
public addSquareOccluder(bounds: Rectangle) {
let tr = new Vector2(bounds.right, bounds.top);
let bl = new Vector2(bounds.left, bounds.bottom);
let br = new Vector2(bounds.right, bounds.bottom);
this.addSegment(bounds.location, tr);
this.addSegment(tr, br);
this.addSegment(br, bl);
this.addSegment(bl, bounds.location);
}
/**
* 添加一个段,第一个点在可视化中显示,但第二个点不显示。
* 每个端点都是两个段的一部分,但我们希望只显示一次
* @param p1
* @param p2
*/
public addSegment(p1: Vector2, p2: Vector2) {
let segment = new Segment();
let endPoint1 = new EndPoint();
let endPoint2 = new EndPoint();
endPoint1.position = p1;
endPoint1.segment = segment;
endPoint2.position = p2;
endPoint2.segment = segment;
segment.p1 = endPoint1;
segment.p2 = endPoint2;
this._segments.push(segment);
this._endPoints.push(endPoint1);
this._endPoints.push(endPoint2);
}
/**
* 移除所有的遮挡物
*/
public clearOccluders(){
this._segments.length = 0;
this._endPoints.length = 0;
}
/**
* 为计算机计算当前的聚光做好准备
* @param origin
* @param radius
*/
public begin(origin: Vector2, radius: number){
this._origin = origin;
this._radius = radius;
this._isSpotLight = false;
}
/**
* 计算可见性多边形并返回三角形扇形的顶点减去中心顶点。返回的数组来自ListPool
*/
public end(): Vector2[] {
let output = ListPool.obtain<Vector2>();
this.updateSegments();
this._endPoints.sort(this._radialComparer.compare);
let currentAngle = 0;
// 在扫描开始时,我们想知道哪些段是活动的。
// 最简单的方法是先进行一次段的收集,然后再进行一次段的收集和处理。
// 然而,更有效的方法是通过所有的段,找出哪些段与最初的扫描线相交,然后对它们进行分类
for (let pass = 0; pass < 2; pass ++) {
for (let p of this._endPoints) {
let currentOld = VisibilityComputer._openSegments.size() == 0 ? null : VisibilityComputer._openSegments.getHead().element;
if (p.begin) {
// 在列表中的正确位置插入
let node = VisibilityComputer._openSegments.getHead();
while (node != null && this.isSegmentInFrontOf(p.segment, node.element, this._origin))
node = node.next;
if (node == null)
VisibilityComputer._openSegments.push(p.segment);
else
VisibilityComputer._openSegments.insert(p.segment, VisibilityComputer._openSegments.indexOf(node.element));
} else {
VisibilityComputer._openSegments.remove(p.segment);
}
let currentNew = null;
if (VisibilityComputer._openSegments.size() != 0)
currentNew = VisibilityComputer._openSegments.getHead().element;
if (currentOld != currentNew) {
if (pass == 1) {
if (!this._isSpotLight || (VisibilityComputer.between(currentAngle, this._spotStartAngle, this._spotEndAngle) &&
VisibilityComputer.between(p.angle, this._spotStartAngle, this._spotEndAngle)))
this.addTriangle(output, currentAngle, p.angle, currentOld);
}
currentAngle = p.angle;
}
}
}
VisibilityComputer._openSegments.clear();
this.clearOccluders();
return output;
}
public addTriangle(triangles: Vector2[], angle1: number, angle2: number, segment: Segment) {
let p1 = this._origin.clone();
let p2 = new Vector2(this._origin.x + Math.cos(angle1), this._origin.y + Math.sin(angle1));
let p3 = Vector2.zero;
let p4 = Vector2.zero;
if (segment != null){
// 将三角形停在相交线段上
p3.x = segment.p1.position.x;
p3.y = segment.p1.position.y;
p4.x = segment.p2.position.x;
p4.y = segment.p2.position.y;
} else {
p3.x = this._origin.x + Math.cos(angle1) * this._radius * 2;
p3.y = this._origin.y + Math.sin(angle1) * this._radius * 2;
p4.x = this._origin.x + Math.cos(angle2) * this._radius * 2;
p4.y = this._origin.y + Math.sin(angle2) * this._radius * 2;
}
let pBegin = VisibilityComputer.lineLineIntersection(p3, p4, p1, p2);
p2.x = this._origin.x + Math.cos(angle2);
p2.y = this._origin.y + Math.sin(angle2);
let pEnd = VisibilityComputer.lineLineIntersection(p3, p4, p1, p2);
triangles.push(pBegin);
triangles.push(pEnd);
}
/**
* 计算直线p1-p2与p3-p4的交点
* @param p1
* @param p2
* @param p3
* @param p4
*/
public static lineLineIntersection(p1: Vector2, p2: Vector2, p3: Vector2, p4: Vector2){
let s = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x))
/ ((p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y));
return new Vector2(p1.x + s * (p2.x - p1.x), p1.y + s * (p2.y - p1.y));
}
public static between(value: number, min: number, max: number) {
value = (360 + (value % 360)) % 360;
min = (3600000 + min) % 360;
max = (3600000 + max) % 360;
if (min < max)
return min <= value && value <= max;
return min <= value || value <= max;
}
/**
* 辅助函数,用于沿外周线构建分段,以限制光的半径。
*/
public loadRectangleBoundaries(){
this.addSegment(new Vector2(this._origin.x - this._radius, this._origin.y - this._radius),
new Vector2(this._origin.x + this._radius, this._origin.y - this._radius);
this.addSegment(new Vector2(this._origin.x - this._radius, this._origin.y + this._radius),
new Vector2(this._origin.x + this._radius, this._origin.y + this._radius));
this.addSegment(new Vector2(this._origin.x - this._radius, this._origin.y - this._radius),
new Vector2(this._origin.x - this._radius, this._origin.y + this._radius));
this.addSegment(new Vector2(this._origin.x + this._radius, this._origin.y - this._radius),
new Vector2(this._origin.x + this._radius, this._origin.y + this._radius));
}
/**
* 助手我们知道a段在b的前面吗实现不反对称也就是说isSegmentInFrontOf(a, b) != (!isSegmentInFrontOf(b, a)))。
* 另外要注意的是,在可见性算法中,它只需要在有限的一组情况下工作,我不认为它能处理所有的情况。
* 见http://www.redblobgames.com/articles/visibility/segment-sorting.html
* @param a
* @param b
* @param relativeTo
*/
public isSegmentInFrontOf(a: Segment, b: Segment, relativeTo: Vector2) {
// 注意:我们稍微缩短了段,所以在这个算法中,端点的交点(共同)不计入交点。
let a1 = VisibilityComputer.isLeftOf(a.p2.position, a.p1.position, VisibilityComputer.interpolate(b.p1.position, b.p2.position, 0.01));
let a2 = VisibilityComputer.isLeftOf(a.p2.position, a.p1.position, VisibilityComputer.interpolate(b.p2.position, b.p1.position, 0.01));
let a3 = VisibilityComputer.isLeftOf(a.p2.position, a.p1.position, relativeTo);
let b1 = VisibilityComputer.isLeftOf(b.p2.position, b.p1.position, VisibilityComputer.interpolate(a.p1.position, a.p2.position, 0.01));
let b2 = VisibilityComputer.isLeftOf(b.p2.position, b.p1.position, VisibilityComputer.interpolate(a.p2.position, a.p1.position, 0.01));
let b3 = VisibilityComputer.isLeftOf(b.p2.position, b.p1.position, relativeTo);
// 注考虑A1-A2这条线。如果B1和B2都在一条边上而relativeTo在另一条边上那么A就在观看者和B之间。
if (b1 == b2 && b2 != b3)
return true;
if (a1 == a2 && a2 == a3)
return true;
if (a1 == a2 && a2 != a3)
return false;
if (b1 == b2 && b2 == b3)
return false;
// 如果A1 !=A2B1 !=B2那么我们就有一个交点。
// 一个更稳健的实现是在交叉点上分割段,使一部分段在前面,一部分段在后面,但无论如何我们不应该有重叠的碰撞器,所以这不是太重要
return false;
// 注意以前的实现方式是a.d < b.d.,这比较简单,但当段的大小不一样时,就麻烦了。
// 如果你是在一个网格上,而且段的大小相似,那么使用距离将是一个更简单和更快的实现。
}
/**
* 返回略微缩短的向量p * (1 - f) + q * f
* @param p
* @param q
* @param f
*/
public static interpolate(p: Vector2, q: Vector2, f: number){
return new Vector2(p.x * (1 - f) + q.x * f, p.y * (1 - f) + q.y * f);
}
/**
* 返回点是否在直线p1-p2的 "左边"。
* @param p1
* @param p2
* @param point
*/
public static isLeftOf(p1: Vector2, p2: Vector2, point: Vector2) {
let cross = (p2.x - p1.x) * (point.y - p1.y)
- (p2.y - p1.y) * (point.x - p1.x);
return cross < 0;
}
/**
* 处理片段,以便我们稍后对它们进行分类
*/
public updateSegments(){
for (let segment of this._segments) {
// 注未来的优化我们可以记录象限和y/x或x/y比率并按象限、比率排序而不是调用atan2。
// 参见<https://github.com/mikolalysenko/compare-slope>,有一个库可以做到这一点
segment.p1.angle = Math.atan2(segment.p1.position.y - this._origin.y, segment.p1.position.x - this._origin.x);
segment.p2.angle = Math.atan2(segment.p2.position.y - this._origin.y, segment.p2.position.x - this._origin.x);
// Pi和Pi之间的映射角度
let dAngle = segment.p2.angle - segment.p1.angle;
if (dAngle <= -Math.PI)
dAngle += Math.PI * 2;
if (dAngle > Math.PI)
dAngle -= Math.PI * 2;
segment.p1.begin = (dAngle > 0);
segment.p2.begin = !segment.p1.begin;
}
// 如果我们有一个聚光灯,我们需要存储前两个段的角度。
// 这些是光斑的边界,我们将用它们来过滤它们之外的任何顶点。
if (this._isSpotLight) {
this._spotStartAngle = this._segments[0].p2.angle;
this._spotEndAngle = this._segments[1].p2.angle;
}
}
}
}