Line data Source code
1 : /**
2 : * The class allows the player to position structures so that they are aligned
3 : * with nearby structures.
4 : */
5 : class ObstructionSnap
6 : {
7 : getValidEdges(allEdges, position, maxSide)
8 : {
9 0 : let edges = [];
10 0 : let dir1 = new Vector2D();
11 0 : let dir2 = new Vector2D();
12 0 : for (let edge of allEdges)
13 : {
14 0 : let signedDistance = Vector2D.dot(edge.normal, position) -
15 : Vector2D.dot(edge.normal, edge.begin);
16 : // Negative signed distance means that the template position
17 : // lays behind the edge.
18 0 : if (signedDistance < -this.MinimalDistanceToSnap - maxSide ||
19 : signedDistance > this.MinimalDistanceToSnap + maxSide)
20 0 : continue;
21 0 : dir1.setFrom(edge.begin).sub(edge.end).normalize();
22 0 : dir2.setFrom(dir1).mult(-1);
23 0 : let offsetDistance = Math.max(
24 : Vector2D.dot(dir1, position) - Vector2D.dot(dir1, edge.begin),
25 : Vector2D.dot(dir2, position) - Vector2D.dot(dir2, edge.end));
26 0 : if (offsetDistance > this.MinimalDistanceToSnap + maxSide)
27 0 : continue;
28 : // If a projection of the template position on the edge is
29 : // lying inside the edge then obviously we don't need to
30 : // account the offset distance.
31 0 : if (offsetDistance < 0)
32 0 : offsetDistance = 0;
33 0 : edge.signedDistance = signedDistance;
34 0 : edge.offsetDistance = offsetDistance;
35 0 : edges.push(edge);
36 : }
37 0 : return edges;
38 : }
39 :
40 : // We need a small padding to avoid unnecessary collisions
41 : // because of loss of accuracy.
42 : getPadding(edge)
43 : {
44 0 : const snapPadding = 0.05;
45 : // We don't need to padding for edges with normals directed inside
46 : // its entity, as we try to snap from an internal side of the edge.
47 0 : return edge.order == "ccw" ? 0 : snapPadding;
48 : }
49 :
50 : // Pick a base edge, it will be the first axis and fix the angle.
51 : // We can't just pick an edge by signed distance, because we might have
52 : // a case when one segment is closer by signed distance than another
53 : // one but much farther by actual (euclid) distance.
54 : compareEdges(a, b)
55 : {
56 0 : const behindA = a.signedDistance < -this.EPS;
57 0 : const behindB = b.signedDistance < -this.EPS;
58 0 : const scoreA = Math.abs(a.signedDistance) + a.offsetDistance;
59 0 : const scoreB = Math.abs(b.signedDistance) + b.offsetDistance;
60 0 : if (Math.abs(scoreA - scoreB) < this.EPS)
61 : {
62 0 : if (behindA != behindB)
63 0 : return behindA - behindB;
64 0 : if (!behindA)
65 0 : return a.offsetDistance - b.offsetDistance;
66 0 : return -a.signedDistance - -b.signedDistance;
67 : }
68 0 : return scoreA - scoreB;
69 : }
70 :
71 : getNearestSizeAlongNormal(width, depth, angle, normal)
72 : {
73 : // Front face direction.
74 0 : let direction = new Vector2D(0.0, 1.0);
75 0 : direction.rotate(angle);
76 0 : let dot = direction.dot(normal);
77 0 : const threshold = Math.cos(Math.PI / 4.0);
78 0 : if (Math.abs(dot) > threshold)
79 0 : return [depth, width];
80 0 : return [width, depth];
81 : }
82 :
83 : getPosition(data, template)
84 : {
85 0 : if (!data.snapToEdges || !template.Obstruction || !template.Obstruction.Static)
86 0 : return undefined;
87 :
88 0 : const width = template.Obstruction.Static["@width"] / 2;
89 0 : const depth = template.Obstruction.Static["@depth"] / 2;
90 0 : const maxSide = Math.max(width, depth);
91 0 : let templatePos = Vector2D.from3D(data);
92 0 : let templateAngle = data.angle || 0;
93 :
94 0 : let edges = this.getValidEdges(data.snapToEdges, templatePos, maxSide);
95 0 : if (!edges.length)
96 0 : return undefined;
97 :
98 0 : let baseEdge = edges[0];
99 0 : for (let edge of edges)
100 0 : if (this.compareEdges(edge, baseEdge) < 0)
101 0 : baseEdge = edge;
102 : // Now we have the normal, we need to determine an angle,
103 : // which side will be snapped first.
104 0 : for (let dir = 0; dir < 4; ++dir)
105 : {
106 0 : const angleCandidate = baseEdge.angle + dir * Math.PI / 2;
107 : // We need to find a minimal angle difference.
108 0 : let difference = Math.abs(angleCandidate - templateAngle);
109 0 : difference = Math.min(difference, Math.PI * 2 - difference);
110 0 : if (difference < Math.PI / 4 + this.EPS)
111 : {
112 0 : templateAngle = angleCandidate;
113 0 : break;
114 : }
115 : }
116 : let [sizeToBaseEdge, sizeToPairedEdge] =
117 0 : this.getNearestSizeAlongNormal(width, depth, templateAngle, baseEdge.normal);
118 :
119 0 : let distance = Vector2D.dot(baseEdge.normal, templatePos) - Vector2D.dot(baseEdge.normal, baseEdge.begin);
120 0 : templatePos.sub(Vector2D.mult(baseEdge.normal, distance - sizeToBaseEdge - this.getPadding(baseEdge)));
121 0 : edges = this.getValidEdges(data.snapToEdges, templatePos, maxSide);
122 0 : if (edges.length > 1)
123 : {
124 0 : let pairedEdges = [];
125 0 : for (let edge of edges)
126 : {
127 : // We have to place a rectangle, so the angle between
128 : // edges should be 90 degrees.
129 0 : if (Math.abs(Vector2D.dot(baseEdge.normal, edge.normal)) > this.EPS)
130 0 : continue;
131 0 : let newEdge = {
132 : "begin": edge.end,
133 : "end": edge.begin,
134 : "normal": Vector2D.mult(edge.normal, -1),
135 : "signedDistance": -edge.signedDistance,
136 : "offsetDistance": edge.offsetDistance,
137 : "order": "ccw",
138 : };
139 0 : pairedEdges.push(edge);
140 0 : pairedEdges.push(newEdge);
141 : }
142 0 : pairedEdges.sort(this.compareEdges.bind(this));
143 0 : if (pairedEdges.length)
144 : {
145 0 : let secondEdge = pairedEdges[0];
146 0 : for (let edge of pairedEdges)
147 0 : if (this.compareEdges(edge, secondEdge) < 0)
148 0 : secondEdge = edge;
149 0 : let distance = Vector2D.dot(secondEdge.normal, templatePos) - Vector2D.dot(secondEdge.normal, secondEdge.begin);
150 0 : templatePos.sub(Vector2D.mult(secondEdge.normal, distance - sizeToPairedEdge - this.getPadding(secondEdge)));
151 : }
152 : }
153 0 : return {
154 : "x": templatePos.x,
155 : "z": templatePos.y,
156 : "angle": templateAngle
157 : };
158 : }
159 : }
160 :
161 1 : ObstructionSnap.prototype.MinimalDistanceToSnap = 5;
162 :
163 1 : ObstructionSnap.prototype.EPS = 1e-3;
164 :
165 1 : Engine.RegisterGlobal("ObstructionSnap", ObstructionSnap);
|