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