-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnavMeshTest.lua
485 lines (432 loc) · 16.1 KB
/
navMeshTest.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
--[[
Copyright © Savoshchanka Anton Aleksandrovich, 2015
version 0.0.4 alpha
HELP:
+ https://love2d.org/forums/viewtopic.php?f=5&t=81229
+ clipperTest version 0.0.4
TODO:
- TODO1 рефакторинг кода
- Class
-+ Cell
-+ Polygon
-+ Obstacle
-+ внедрить
- тестирование
-+ поработать над thisModule.result
-+ полигоны нужно чтоб были Class Polygon, вместо обычной таблицы
-+ можно сделать Class Cell
-+ алгоритм
-+ работаем clipper-ом
-+ для каждого полигона проверяем внутри ли он остальных полигонов (смотри CutHolesTest.cut.isPolygonInPolygon)
-+ если да, то
-+?YES version 1
-+ запоминаем его как дырку-полигон в вырезаемом полигоне
- шаг
-? вырезаем и обновляем результат для полигона с дыркой
-? удаляем из cell;
- не обязательно делать этот шаг, можно просто запомнить этот полигон как дырку
-+ результат с вырезанной дыркой используем только для поиска пути или рисования, не для вырезания (смотри CutHolesTest.cut.cutHoles)
-?NO version 2
- вырезаем дырку в нужном полигоне
- если нужно (если полигон вогнутый), то результат разделяем на выпуклые полигоны, и дальше работаем с выпуклыми
+ test Hardoncollider Polygon:splitConvex()
- проблемы с совместимостью с clipper: clean(), simplify()
- вогнутый полигон удаляем из cell
- добавляем новые выпуклые полигоны в cell
- TODO2 разобраться с случай1.png
- как определить последовательность вырезания дырок?
- всегда ли будет правильная последовательность генерируемая clipper?
--]]
--[[
zlib License
Copyright (c) 2015 Savoshchanka Anton Aleksandrovich
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgement in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
--]]
---------------------- test
--local t = {}
----local s = {}
--assert(t and s)
----------------------
local thisModule = {}
local Cell = require('classes.navmesh.Cell')
local Obstacle = require('classes.navmesh.Obstacle')
-------------------------------------------------- clipper
local clipperModule = require("clipper.clipper")
------------- my
--[[
version 0.0.2
--]]
do
clipperModule.executeOperation = {intersection='intersection', union='union', difference='difference', xor='xor'}
clipperModule.fillType = {even_odd='even_odd', non_zero='non_zero', positive='positive', negative='negative'}
clipperModule.checkType = {}
function clipperModule.checkType.clipper_Polygon(var)
if string.find(tostring(var), "clipper_Polygon") and not string.find(tostring(var), "clipper_Polygons") then
return true
else
return false
end
end
function clipperModule.checkType.clipper_Polygons(var)
local result = string.find(tostring(var), "clipper_Polygons")
if result then
result = true
else
result = false
end
return result
end
function clipperModule:newPolygon(tablePoints)
assert(#tablePoints > 0, "#table <= 0")
local clipperPolygon = self.polygon()
for i=1, #tablePoints, 2 do
clipperPolygon:add(tablePoints[i], tablePoints[i+1])
end
return clipperPolygon
end
function clipperModule:newPolygonsList(tablePolygonsObjects)
local empty = true
local clipperPolygonsList = self.polygons()
for k, polygon in pairs(tablePolygonsObjects) do
-- assert(self.checkType.clipper_Polygon(polygon.clipper), "in table variable at key("..tostring(k)..") is not clipper_Polygon or clipper_Polygons type")
if self.checkType.clipper_Polygon(polygon.clipper) then
clipperPolygonsList:add(polygon.clipper)
empty = false
end
end
if empty then return false end
return clipperPolygonsList
end
function clipperModule:clip(subjectPolygon, clipPolygon)
assert(self.checkType.clipper_Polygon(subjectPolygon) or self.checkType.clipper_Polygons(subjectPolygon), "argument1 not clipper_Polygon or clipper_Polygons type")
assert(self.checkType.clipper_Polygon(clipPolygon) or self.checkType.clipper_Polygons(clipPolygon), "argument2 not clipper_Polygon or clipper_Polygons type")
local clO = self.new() -- clipper object
clO:add_subject(subjectPolygon)
clO:add_clip(clipPolygon)
return clO:execute(self.executeOperation.difference, self.fillType.even_odd, self.fillType.even_odd, true)
end
end
------------------------------------------------------------
------------------------- cell
do
thisModule.cell = Cell:newObject()
thisModule.cell:addNewPolygon({vertices = {
200, 110,
600, 110,
600, 510,
200, 510
}})
-- test clean()
-- {
-- 10*1, 10*1,
-- 100*1, 10*1,
-- 100*1, 100*1,
-- 10*1, 100*1,
-- 99*1, 99*1
-- }
end
-------------------------- obstacle
do
thisModule.obstacle = Obstacle:newObject()
thisModule.obstacle:addNewPolygon({vertices = {
150, 150,
250, 150,
250, 250,
150, 250
}})
thisModule.obstacle:addNewPolygon({vertices = {
0, 0,
1, 0,
1, 1,
0, 1
}})
end
-------------------------- result
do
thisModule.result = {}
thisModule.result.polygons = {}
function thisModule.result:refreshFromClipperResult()
self.polygons = {}
for polyN1=1, clipperModule.result:size() do
local clipperPolygons = clipperModule.result:get(polyN1)
clipperPolygons = clipperModule.polygons(clipperPolygons)
clipperPolygons = clipperPolygons:clean() -- try FIX2 BUG3
clipperPolygons = clipperPolygons:simplify() -- FIX BUG4
for polyN2=1, clipperPolygons:size() do
local newPolygon = {}
table.insert(self.polygons, newPolygon)
local clipperPolygon = clipperPolygons:get(polyN2)
for pointN3=1, tonumber(clipperPolygon:size()) do
table.insert(newPolygon, tonumber(clipperPolygon:get(pointN3).x))
table.insert(newPolygon, tonumber(clipperPolygon:get(pointN3).y))
end
end
end
end
thisModule.result.draw = {}
thisModule.result.draw.polygons = {}
-- Cell class
thisModule.resultCell = Cell:newObject()
end
function thisModule:clip()
if thisModule.cell.polygonsCount > 0 then
-- refresh clipper polygons
for _, polygon in pairs(thisModule.obstacle.polygons) do
polygon.clipper = clipperModule:newPolygon(polygon.vertices)
end
local p1 = clipperModule:newPolygonsList(thisModule.cell.polygons)
local p2 = clipperModule:newPolygonsList(thisModule.obstacle.polygons)
clipperModule.result = clipperModule:clip(p1, p2)
-- вроде не работает тут как ожидал
-- clipperModule.result = clipperModule.result:clean()
-- clipperModule.result = clipperModule.result:simplify() -- BUG4 смотри картинку
thisModule.result:refreshFromClipperResult()
-- test
if false then
local removeIndexes, polygonsConvex = {}, {}
for i, polygon in ipairs(thisModule.result.polygons) do
if not love.math.isConvex(polygon) then
local concave = require('hardoncollider.polygon')(unpack(polygon))
table.insert(polygonsConvex, concave:splitConvex())
table.insert(removeIndexes, i)
end
end
for i, index in ipairs(removeIndexes) do
table.remove(thisModule.result.polygons, index)
end
for _, polygonList in ipairs(polygonsConvex) do
for _, polygon in ipairs(polygonList) do
table.insert(thisModule.result.polygons, {polygon:unpack()})
end
end
end
-- алгоритм version 1 test
if true then
-- make resultCell polygons
-- недостаток: каждый раз очередь полигонов разная
thisModule.resultCell:deleteAllPolygons()
for i, polygon in ipairs(thisModule.result.polygons) do
local newPolygon = thisModule.resultCell:addNewPolygon({vertices = polygon})
-- print(polygon, newPolygon.vertices)
-- newPolygon.clipper = clipperModule:newPolygon(newPolygon.vertices)
end
for _, polygon1 in pairs(thisModule.resultCell.polygons) do
for _, polygon2 in pairs(thisModule.resultCell.polygons) do
if polygon1 ~= polygon2 and thisModule.cut.isPolygonInPolygon(polygon1.vertices, polygon2.vertices) then
polygon1:addPolygonHole(polygon2)
polygon2.imHole = true
-- print(polygon1.vertices)
end
end
end
-- вырезаем дырки
if true then
thisModule.result.polygons = {}
for _, polygon in pairs(thisModule.resultCell.polygons) do
if not polygon.imHole then
local iHoles = {}
for _, hole in pairs(polygon.cut.polygonHoles) do
table.insert(iHoles, hole.vertices)
end
if #iHoles > 0 then
local ok, out = pcall(thisModule.cut.cutHoles, polygon.vertices, iHoles)
if ok then
polygon.cut.result = out -- вырезаный полигон
-- переводим к thisModule.result, чтобы рисовать
table.insert(thisModule.result.polygons, polygon.cut.result)
else
-- table.insert(thisModule.result.polygons, polygon.vertices)
end
else
table.insert(thisModule.result.polygons, polygon.vertices)
end
else
-- table.insert(thisModule.result.polygons, polygon.vertices)
end
end
end
end
end
end
-------------------------- cut
do
thisModule.cut = {}
thisModule.cut.isPolygonInPolygon = require("math.itraykov.poly").polygon
thisModule.cut.getPolygonIntersection = require("math.mlib.mlib").polygon.getPolygonIntersection
thisModule.cut.cutHoles = require("math.itraykov.poly2").cutholes
end
---------------------------------------- test
for _, polygon in pairs(thisModule.cell.polygons) do
polygon.clipper = clipperModule:newPolygon(polygon.vertices)
end
for _, polygon in pairs(thisModule.obstacle.polygons) do
polygon.clipper = clipperModule:newPolygon(polygon.vertices)
end
clipperModule.result = clipperModule:clip(clipperModule:newPolygonsList(thisModule.cell.polygons), clipperModule:newPolygonsList(thisModule.obstacle.polygons))
thisModule.result:refreshFromClipperResult()
-------------------------------------
function thisModule:update(dt)
-- if true then return nil end
----------------------------------------------------------------------------------------- update obstacle
-------------------------- двигаем obstacle
local x, y = love.mouse.getPosition()
thisModule.obstacle:deleteAllPolygons()
thisModule.obstacle:addNewPolygon({vertices = {
x, y,
x+50, y,
x+50, y+50,
x, y+50
}})
thisModule.obstacle:addNewPolygon({vertices = {
0, 0,
1, 0,
1, 1,
0, 1
}})
--------------------------- clipper
if true then
thisModule:clip()
-- FIX1 BUG3 (see image)
if true then
local fixBUG3 = {}
fixBUG3.need = false
for i, polygon in ipairs(thisModule.result.polygons) do
local triangles
local ok, out = pcall(love.math.triangulate, polygon)
if not ok then
-- cant draw(triangulate) result.polygons
fixBUG3.need = true
break
end
end
if fixBUG3.need then
------------------------------ scale obstacle to +1
thisModule.obstacle:deleteAllPolygons()
fixBUG3.x, fixBUG3.y = x-1, y-1
thisModule.obstacle:addNewPolygon({vertices = {
fixBUG3.x, fixBUG3.y,
fixBUG3.x+50+2, fixBUG3.y,
fixBUG3.x+50+2, fixBUG3.y+50+2,
fixBUG3.x, fixBUG3.y+50+2
}})
thisModule.obstacle:addNewPolygon({vertices = {
0, 0,
1, 0,
1, 1,
0, 1
}})
thisModule:clip()
------------------------------- перепроверяем
for i, polygon in ipairs(thisModule.result.polygons) do
local triangles
local ok, out = pcall(love.math.triangulate, polygon)
if not ok then
-- cant draw(triangulate) result.polygons
fixBUG3.need = false
break
else
-- если проблема исправлена
print(os.clock(), 'fixed bug 3', x, y)
end
end
-------------------------------- если проблема не исправлена, то отменяем предыдущий результат; чтобы было все точно, без лишнего увеличения obstacle
if not fixBUG3.need then
thisModule.obstacle:deleteAllPolygons()
thisModule.obstacle:addNewPolygon({vertices = {
x, y,
x+50, y,
x+50, y+50,
x, y+50
}})
thisModule.obstacle:addNewPolygon({vertices = {
0, 0,
1, 0,
1, 1,
0, 1
}})
thisModule:clip()
end
end
end
end
end
function thisModule:mousePressed(x, y, button)
if button == 'l' then
-- -- при нажатии на кнопку мыши запоминаем вырезаную cell, и уже вырезаем в ней в дальнейшем
-- thisModule.cell.polygons = thisModule.result.polygons
-- do
-- thisModule.cell.clipperPolygons = {}
-- for i, polygon in ipairs(thisModule.cell.polygons) do
-- table.insert(thisModule.cell.clipperPolygons, clipperModule:newPolygon(polygon))
-- end
---- print(#thisModule.cell.polygons)
-- end
-- copy result
thisModule.cell:deleteAllPolygons()
-- for _, polygon in pairs(thisModule.result.polygons) do
-- local newPolygon = thisModule.cell:addNewPolygon({vertices = polygon})
-- newPolygon.clipper = clipperModule:newPolygon(newPolygon.vertices)
-- end
for _, polygon in pairs(thisModule.resultCell.polygons) do
local newPolygon = thisModule.cell:addNewPolygon({vertices = polygon.vertices})
newPolygon.clipper = clipperModule:newPolygon(newPolygon.vertices)
end
end
end
function thisModule:draw()
-- cell.polygons
if false then
love.graphics.setColor(0, 255, 0, 255)
local ok, out = pcall(thisModule.cell.draw, thisModule.cell)
if ok then
else
love.graphics.print('cant draw(triangulate) cell.polygons: '..out, 0, 0, 0, 1, 1)
end
end
love.graphics.setLineWidth(2)
love.graphics.setLineStyle('rough')
love.graphics.setLineJoin('none')
------------------------------------------------------ thisModule.result.polygons
if true then
for i, polygon in ipairs(thisModule.result.polygons) do
local triangles
local ok, out = pcall(love.math.triangulate, polygon)
if ok then
triangles = out
love.graphics.setColor(0, 0, 255, 255)
for i1, triangle in ipairs(triangles) do
love.graphics.polygon('fill', triangle)
end
-- love.graphics.setColor(255, 0, 0, 255)
-- love.graphics.print(i, polygon[1], polygon[2], 0, 1.5, 1.5)
else
love.graphics.setColor(0, 0, 255, 255)
love.graphics.print('cant draw(triangulate) result.polygons', 0, 20, 0, 1, 1)
end
end
end
love.graphics.setLineStyle('smooth')
love.graphics.setLineWidth(1)
love.graphics.setColor(255, 0, 0, 255)
thisModule.obstacle:draw()
love.graphics.setColor(0, 255, 0, 255)
love.graphics.print('cell.polygonsCount = '..thisModule.cell.polygonsCount, 0, 40, 0, 1, 1)
love.graphics.setColor(0, 0, 255, 255)
love.graphics.print('clipper.result:size() = '..clipperModule.result:size(), 0, 60, 0, 1, 1)
love.graphics.print('#result.polygons = '..#thisModule.result.polygons, 0, 80, 0, 1, 1)
local mx, my = love.mouse.getPosition()
love.graphics.print('mouse position() = '..mx..', '..my, 0, 100, 0, 1, 1)
end
return thisModule