1 unit uLandGenTemplateBased; |
|
2 interface |
|
3 |
|
4 uses uLandTemplates, uLandOutline; |
|
5 |
|
6 procedure GenTemplated(var Template: TEdgeTemplate); |
|
7 procedure DivideEdges(fillPointsCount: LongWord; var pa: TPixAr); |
|
8 |
|
9 var minDistance, dabDiv: LongInt; // different details size |
|
10 |
|
11 implementation |
|
12 uses {$IFDEF IPHONEOS}uTypes, {$ENDIF} uVariables, uConsts, uFloat, uLandUtils, uRandom, SDLh, math; |
|
13 |
|
14 |
|
15 procedure SetPoints(var Template: TEdgeTemplate; var pa: TPixAr; fps: PPointArray); |
|
16 var i: LongInt; |
|
17 begin |
|
18 with Template do |
|
19 begin |
|
20 pa.Count:= BasePointsCount; |
|
21 for i:= 0 to pred(LongInt(pa.Count)) do |
|
22 begin |
|
23 pa.ar[i].x:= BasePoints^[i].x + LongInt(GetRandom(BasePoints^[i].w)); |
|
24 if pa.ar[i].x <> NTPX then |
|
25 pa.ar[i].x:= pa.ar[i].x + ((LAND_WIDTH - Template.TemplateWidth) div 2); |
|
26 pa.ar[i].y:= BasePoints^[i].y + LongInt(GetRandom(BasePoints^[i].h)) + LAND_HEIGHT - LongInt(Template.TemplateHeight) |
|
27 end; |
|
28 |
|
29 if canMirror then |
|
30 if getrandom(2) = 0 then |
|
31 begin |
|
32 for i:= 0 to pred(BasePointsCount) do |
|
33 if pa.ar[i].x <> NTPX then |
|
34 pa.ar[i].x:= LAND_WIDTH - 1 - pa.ar[i].x; |
|
35 for i:= 0 to pred(FillPointsCount) do |
|
36 fps^[i].x:= LAND_WIDTH - 1 - fps^[i].x; |
|
37 end; |
|
38 |
|
39 (* Experiment in making this option more useful |
|
40 if ((not isNegative) and (cTemplateFilter = 4)) or |
|
41 (canFlip and (getrandom(2) = 0)) then |
|
42 begin |
|
43 for i:= 0 to pred(BasePointsCount) do |
|
44 begin |
|
45 pa.ar[i].y:= LAND_HEIGHT - 1 - pa.ar[i].y + (LAND_HEIGHT - TemplateHeight) * 2; |
|
46 if pa.ar[i].y > LAND_HEIGHT - 1 then |
|
47 pa.ar[i].y:= LAND_HEIGHT - 1; |
|
48 end; |
|
49 for i:= 0 to pred(FillPointsCount) do |
|
50 begin |
|
51 FillPoints^[i].y:= LAND_HEIGHT - 1 - FillPoints^[i].y + (LAND_HEIGHT - TemplateHeight) * 2; |
|
52 if FillPoints^[i].y > LAND_HEIGHT - 1 then |
|
53 FillPoints^[i].y:= LAND_HEIGHT - 1; |
|
54 end; |
|
55 end; |
|
56 end |
|
57 *) |
|
58 // template recycling. Pull these off the floor a bit |
|
59 if (not isNegative) and (cTemplateFilter = 4) then |
|
60 begin |
|
61 for i:= 0 to pred(BasePointsCount) do |
|
62 begin |
|
63 dec(pa.ar[i].y, 100); |
|
64 if pa.ar[i].y < 0 then |
|
65 pa.ar[i].y:= 0; |
|
66 end; |
|
67 for i:= 0 to pred(FillPointsCount) do |
|
68 begin |
|
69 dec(fps^[i].y, 100); |
|
70 if fps^[i].y < 0 then |
|
71 fps^[i].y:= 0; |
|
72 end; |
|
73 end; |
|
74 |
|
75 if (canFlip and (getrandom(2) = 0)) then |
|
76 begin |
|
77 for i:= 0 to pred(BasePointsCount) do |
|
78 pa.ar[i].y:= LAND_HEIGHT - 1 - pa.ar[i].y; |
|
79 for i:= 0 to pred(FillPointsCount) do |
|
80 fps^[i].y:= LAND_HEIGHT - 1 - fps^[i].y; |
|
81 end; |
|
82 end |
|
83 end; |
|
84 |
|
85 procedure FindPoint(si: LongInt; fillPointsCount: LongWord; var newPoint: TPoint; var pa: TPixAr); |
|
86 const mapBorderMargin = 40; |
|
87 var p1, p2, p4, fp, mp: TPoint; |
|
88 i, t1, t2, iy, ix, aqpb: LongInt; |
|
89 a, b, p, q: LongInt; |
|
90 dab, d, distL, distR: LongInt; |
|
91 begin |
|
92 // [p1, p2] is the segment we're trying to divide |
|
93 p1:= pa.ar[si]; |
|
94 p2:= pa.ar[si + 1]; |
|
95 |
|
96 if p2.x = NTPX then |
|
97 // it is segment from last to first point, so need to find first point |
|
98 begin |
|
99 i:= si - 2; |
|
100 while (i >= 0) and (pa.ar[i].x <> NTPX) do |
|
101 dec(i); |
|
102 p2:= pa.ar[i + 1] |
|
103 end; |
|
104 |
|
105 // perpendicular vector |
|
106 a:= p2.y - p1.y; |
|
107 b:= p1.x - p2.x; |
|
108 dab:= DistanceI(a, b).Round; |
|
109 |
|
110 // its middle point |
|
111 mp.x:= (p1.x + p2.x) div 2; |
|
112 mp.y:= (p1.y + p2.y) div 2; |
|
113 |
|
114 // don't process too short segments or those which are too close to map borders |
|
115 if (p1.x = NTPX) |
|
116 or (dab < minDistance * 3) |
|
117 or (mp.x < leftX + mapBorderMargin) |
|
118 or (mp.x > rightX - mapBorderMargin) |
|
119 or (mp.y < topY + mapBorderMargin) |
|
120 or (mp.y > LongInt(LAND_HEIGHT) - mapBorderMargin) |
|
121 then |
|
122 begin |
|
123 newPoint:= p1; |
|
124 exit; |
|
125 end; |
|
126 |
|
127 // find distances to map borders |
|
128 if a <> 0 then |
|
129 begin |
|
130 // left border |
|
131 iy:= (leftX + mapBorderMargin - mp.x) * b div a + mp.y; |
|
132 d:= DistanceI(mp.x - leftX - mapBorderMargin, mp.y - iy).Round; |
|
133 t1:= a * (mp.x - mapBorderMargin) + b * (mp.y - iy); |
|
134 if t1 > 0 then distL:= d else distR:= d; |
|
135 |
|
136 // right border |
|
137 iy:= (rightX - mapBorderMargin - mp.x) * b div a + mp.y; |
|
138 d:= DistanceI(mp.x - rightX + mapBorderMargin, mp.y - iy).Round; |
|
139 if t1 > 0 then distR:= d else distL:= d; |
|
140 end else |
|
141 begin |
|
142 distL:= LAND_WIDTH + LAND_HEIGHT; |
|
143 distR:= distL; |
|
144 end; |
|
145 |
|
146 if b <> 0 then |
|
147 begin |
|
148 // top border |
|
149 ix:= (topY + mapBorderMargin - mp.y) * a div b + mp.x; |
|
150 d:= DistanceI(mp.y - topY - mapBorderMargin, mp.x - ix).Round; |
|
151 t2:= b * (mp.y - mapBorderMargin) + a * (mp.x - ix); |
|
152 if t2 > 0 then distL:= min(d, distL) else distR:= min(d, distR); |
|
153 |
|
154 // bottom border |
|
155 ix:= (LAND_HEIGHT - mapBorderMargin - mp.y) * a div b + mp.x; |
|
156 d:= DistanceI(mp.y - LAND_HEIGHT + mapBorderMargin, mp.x - ix).Round; |
|
157 if t2 > 0 then distR:= min(d, distR) else distL:= min(d, distL); |
|
158 end; |
|
159 |
|
160 // now go through all other segments |
|
161 fp:= pa.ar[0]; |
|
162 for i:= 0 to LongInt(pa.Count) - 2 do |
|
163 if pa.ar[i].x = NTPX then |
|
164 fp:= pa.ar[i + 1] |
|
165 else if (i <> si) then |
|
166 begin |
|
167 p4:= pa.ar[i + 1]; |
|
168 if p4.x = NTPX then |
|
169 p4:= fp; |
|
170 |
|
171 // check if it intersects |
|
172 t1:= (mp.x - pa.ar[i].x) * b - a * (mp.y - pa.ar[i].y); |
|
173 t2:= (mp.x - p4.x) * b - a * (mp.y - p4.y); |
|
174 |
|
175 if (t1 > 0) <> (t2 > 0) then // yes it does, hard arith follows |
|
176 begin |
|
177 p:= p4.x - pa.ar[i].x; |
|
178 q:= p4.y - pa.ar[i].y; |
|
179 aqpb:= a * q - p * b; |
|
180 |
|
181 if (aqpb <> 0) then |
|
182 begin |
|
183 // (ix; iy) is intersection point |
|
184 iy:= (((Int64(pa.ar[i].x) - mp.x) * b + Int64(mp.y) * a) * q - Int64(pa.ar[i].y) * p * b) div aqpb; |
|
185 if abs(b) > abs(q) then |
|
186 ix:= (iy - mp.y) * a div b + mp.x |
|
187 else |
|
188 ix:= (iy - pa.ar[i].y) * p div q + pa.ar[i].x; |
|
189 |
|
190 d:= DistanceI(mp.y - iy, mp.x - ix).Round; |
|
191 t1:= b * (mp.y - iy) + a * (mp.x - ix); |
|
192 if t1 > 0 then distL:= min(d, distL) else distR:= min(d, distR); |
|
193 end; |
|
194 end; |
|
195 end; |
|
196 |
|
197 // go through all points, including fill points |
|
198 for i:= 0 to Pred(LongInt(pa.Count + fillPointsCount)) do |
|
199 // if this point isn't on current segment |
|
200 if (si <> i) and (i <> si + 1) and (pa.ar[i].x <> NTPX) then |
|
201 begin |
|
202 // also check intersection with rays through pa.ar[i] if this point is good |
|
203 t1:= (p1.x - pa.ar[i].x) * b - a * (p1.y - pa.ar[i].y); |
|
204 t2:= (p2.x - pa.ar[i].x) * b - a * (p2.y - pa.ar[i].y); |
|
205 if (t1 > 0) <> (t2 > 0) then |
|
206 begin |
|
207 // ray from p1 |
|
208 p:= pa.ar[i].x - p1.x; |
|
209 q:= pa.ar[i].y - p1.y; |
|
210 aqpb:= a * q - p * b; |
|
211 |
|
212 if (aqpb <> 0) then |
|
213 begin |
|
214 // (ix; iy) is intersection point |
|
215 iy:= (((Int64(p1.x) - mp.x) * b + Int64(mp.y) * a) * q - Int64(p1.y) * p * b) div aqpb; |
|
216 if abs(b) > abs(q) then |
|
217 ix:= (iy - mp.y) * a div b + mp.x |
|
218 else |
|
219 ix:= (iy - p1.y) * p div q + p1.x; |
|
220 |
|
221 d:= DistanceI(mp.y - iy, mp.x - ix).Round; |
|
222 t1:= b * (mp.y - iy) + a * (mp.x - ix); |
|
223 if t1 > 0 then distL:= min(d, distL) else distR:= min(d, distR); |
|
224 end; |
|
225 |
|
226 // and ray from p2 |
|
227 p:= pa.ar[i].x - p2.x; |
|
228 q:= pa.ar[i].y - p2.y; |
|
229 aqpb:= a * q - p * b; |
|
230 |
|
231 if (aqpb <> 0) then |
|
232 begin |
|
233 // (ix; iy) is intersection point |
|
234 iy:= (((Int64(p2.x) - mp.x) * b + Int64(mp.y) * a) * q - Int64(p2.y) * p * b) div aqpb; |
|
235 if abs(b) > abs(q) then |
|
236 ix:= (iy - mp.y) * a div b + mp.x |
|
237 else |
|
238 ix:= (iy - p2.y) * p div q + p2.x; |
|
239 |
|
240 d:= DistanceI(mp.y - iy, mp.x - ix).Round; |
|
241 t2:= b * (mp.y - iy) + a * (mp.x - ix); |
|
242 if t2 > 0 then distL:= min(d, distL) else distR:= min(d, distR); |
|
243 end; |
|
244 end; |
|
245 end; |
|
246 |
|
247 // don't move new point for more than length of initial segment |
|
248 // adjust/parametrize for more flat surfaces (try values 3/4, 1/2 of dab, or even 1/4) |
|
249 d:= dab * 100 div dabDiv; |
|
250 //d:= dab * (1 + abs(cFeatureSize - 8)) div 6; |
|
251 //d:= dab * (14 + cFeatureSize) div 20; |
|
252 if distL > d then distL:= d; |
|
253 if distR > d then distR:= d; |
|
254 |
|
255 if distR + distL < minDistance * 2 + 10 then |
|
256 begin |
|
257 // limits are too narrow, just divide |
|
258 newPoint.x:= mp.x; |
|
259 newPoint.y:= mp.y; |
|
260 end |
|
261 else |
|
262 begin |
|
263 // select distance within [-distL; distR] |
|
264 d:= -distL + minDistance + LongInt(GetRandom(distR + distL - minDistance * 2)); |
|
265 //d:= distR - minDistance; |
|
266 //d:= - distL + minDistance; |
|
267 |
|
268 // calculate new point |
|
269 newPoint.x:= mp.x + a * d div dab; |
|
270 newPoint.y:= mp.y + b * d div dab; |
|
271 end; |
|
272 end; |
|
273 |
|
274 procedure DivideEdges(fillPointsCount: LongWord; var pa: TPixAr); |
|
275 var i, t: LongInt; |
|
276 newPoint: TPoint; |
|
277 begin |
|
278 newPoint.x:= 0; |
|
279 newPoint.y:= 0; |
|
280 i:= 0; |
|
281 |
|
282 while i < LongInt(pa.Count) - 1 do |
|
283 begin |
|
284 FindPoint(i, fillPointsCount, newPoint, pa); |
|
285 |
|
286 if (newPoint.x <> pa.ar[i].x) or (newPoint.y <> pa.ar[i].y) then |
|
287 begin |
|
288 // point found, free a slot for it in array, don't forget to move appended fill points |
|
289 for t:= pa.Count + fillPointsCount downto i + 2 do |
|
290 pa.ar[t]:= pa.ar[t - 1]; |
|
291 inc(pa.Count); |
|
292 pa.ar[i + 1]:= newPoint; |
|
293 inc(i) |
|
294 end; |
|
295 inc(i) |
|
296 end; |
|
297 end; |
|
298 |
|
299 procedure Distort2(var Template: TEdgeTemplate; fps: PPointArray; var pa: TPixAr); |
|
300 var i: Longword; |
|
301 begin |
|
302 // append fill points to ensure distortion won't move them to other side of segment |
|
303 for i:= 0 to pred(Template.FillPointsCount) do |
|
304 begin |
|
305 pa.ar[pa.Count + i].x:= fps^[i].x; |
|
306 pa.ar[pa.Count + i].y:= fps^[i].y; |
|
307 end; |
|
308 |
|
309 // divide while it divides |
|
310 repeat |
|
311 i:= pa.Count; |
|
312 DivideEdges(Template.FillPointsCount, pa) |
|
313 until i = pa.Count; |
|
314 |
|
315 {$IFDEF IPHONEOS} |
|
316 if GameType <> gmtLandPreview then |
|
317 {$ENDIF} |
|
318 // make it smooth |
|
319 BezierizeEdge(pa, _0_2); |
|
320 end; |
|
321 |
|
322 |
|
323 procedure GenTemplated(var Template: TEdgeTemplate); |
|
324 var pa: TPixAr; |
|
325 i: Longword; |
|
326 y, x: Longword; |
|
327 fps: TPointArray; |
|
328 begin |
|
329 fps:=Template.FillPoints^; |
|
330 ResizeLand(Template.TemplateWidth, Template.TemplateHeight); |
|
331 for y:= 0 to LAND_HEIGHT - 1 do |
|
332 for x:= 0 to LAND_WIDTH - 1 do |
|
333 LandSet(y, x, lfBasic); |
|
334 |
|
335 minDistance:= sqr(cFeatureSize) div 8 + 10; |
|
336 //dabDiv:= getRandom(41)+60; |
|
337 //dabDiv:= getRandom(31)+70; |
|
338 dabDiv:= getRandom(21)+100; |
|
339 MaxHedgehogs:= Template.MaxHedgehogs; |
|
340 hasGirders:= Template.hasGirders; |
|
341 playHeight:= Template.TemplateHeight; |
|
342 playWidth:= Template.TemplateWidth; |
|
343 leftX:= (LAND_WIDTH - playWidth) div 2; |
|
344 rightX:= Pred(leftX + playWidth); |
|
345 topY:= LAND_HEIGHT - playHeight; |
|
346 |
|
347 {$HINTS OFF} |
|
348 SetPoints(Template, pa, @fps); |
|
349 {$HINTS ON} |
|
350 |
|
351 Distort2(Template, @fps, pa); |
|
352 |
|
353 DrawEdge(pa, 0); |
|
354 |
|
355 with Template do |
|
356 for i:= 0 to pred(FillPointsCount) do |
|
357 with fps[i] do |
|
358 FillLand(x, y, 0, 0); |
|
359 |
|
360 DrawEdge(pa, lfBasic); |
|
361 |
|
362 // HACK: force to only cavern even if a cavern map is invertable if cTemplateFilter = 4 ? |
|
363 if (cTemplateFilter = 4) |
|
364 or (Template.canInvert and (getrandom(2) = 0)) |
|
365 or (not Template.canInvert and Template.isNegative) then |
|
366 begin |
|
367 hasBorder:= true; |
|
368 for y:= 0 to LAND_HEIGHT - 1 do |
|
369 for x:= 0 to LAND_WIDTH - 1 do |
|
370 if (y < LongWord(topY)) or (x < LongWord(leftX)) or (x > LongWord(rightX)) then |
|
371 LandSet(y, x, 0) |
|
372 else |
|
373 begin |
|
374 if LandGet(y, x) = 0 then |
|
375 LandSet(y, x, lfBasic) |
|
376 else if LandGet(y, x) = lfBasic then |
|
377 LandSet(y, x, 0); |
|
378 end; |
|
379 end; |
|
380 end; |
|
381 |
|
382 |
|
383 end. |
|