Some improvements and new features. I managed to fix the bug when you were on the square obstacle. In fact, if you're on a segment of an obstacle, you move back one pixel before starting pathfinding; it's the best solution I've found.
You can now activate and deactivate a wall with F1.
I also tested it so you can change the sprite size to give an impression of depth if needed.
Code: Select all
; =======================================================================
; Point'n Click 2D pathFinding — Single-file demo (optimised & documented)
; =======================================================================
; FR: Démo autonome de pathfinding 2D pour jeu point&click.
; EN: Self-contained 2D pathfinding demo for point-and-click games.
;
; Notes
; - Code prêt à diffuser : commentaires FR/EN, logs optionnels, gardes anti cas
; dégénérés, et fonctions documentées (params/retours).
; - Aucune dépendance externe — PureBasic seulement.
; - Les maths restent en entiers pour la compatibilité du rendu PB 2D,
; les distances utilisent des flottants là où nécessaire.
; =======================================================================
; =============================
; Debug core (module + macro)
; =============================
DeclareModule trace
EnableExplicit
; FR: Active/désactive les logs (#DBG) à la compilation.
; EN: Enable/disable build-time logs (#DBG).
#EnableDebug = #True
; -------------------------------------------------------------------
; NewLog(msg, procName, moduleName, lineNumber, fileName)
; FR: Envoie un message formaté dans la console Debug.
; - msg : message libre
; - procName : #PB_Compiler_Procedure (fourni par macro DBG)
; - moduleName : #PB_Compiler_Module (idem)
; - lineNumber : #PB_Compiler_Line (idem)
; - fileName : #PB_Compiler_File (idem)
; EN: Send a formatted log message to the Debug console.
; -------------------------------------------------------------------
Declare NewLog(msg.s, procName.s, moduleName.s, lineNumber.i, fileName.s)
; -------------------------------------------------------------------
; Macro DBG(tag, msg)
; FR: Log compact avec tag facultatif ("" pour aucun).
; EN: Compact logger with optional tag ("" for none).
; -------------------------------------------------------------------
Macro DBG(tag, msg)
CompilerIf trace::#EnableDebug
If tag <> ""
trace::NewLog("[" + tag + "] " + msg, #PB_Compiler_Procedure, #PB_Compiler_Module, #PB_Compiler_Line, #PB_Compiler_File)
Else
trace::NewLog(msg, #PB_Compiler_Procedure, #PB_Compiler_Module, #PB_Compiler_Line, #PB_Compiler_File)
EndIf
CompilerEndIf
EndMacro
EndDeclareModule
Module trace
EnableExplicit
Procedure NewLog(msg.s, procName.s, moduleName.s, lineNumber.i, fileName.s)
Protected tagText.s
; FR: Étiquette [Module::Proc] si dispo, sinon fallback.
; EN: Label [Module::Proc] if available, otherwise a fallback.
If procName <> ""
If moduleName <> ""
tagText = "[" + moduleName + "::" + procName + "]"
Else
tagText = "[" + procName + "]"
EndIf
Else
If moduleName <> ""
tagText = "[" + moduleName + "]"
Else
tagText = "[top]"
EndIf
EndIf
Protected shortName.s = GetFilePart(fileName)
Debug tagText + " " + msg + " (" + shortName + ":" + Str(lineNumber) + ")"
EndProcedure
EndModule
; =======================================================================
; Module: vector
; =======================================================================
DeclareModule vector
EnableExplicit
; FR: Point/Vector entier (x,y) — suffisant pour l’écran PB.
; EN: Integer point/vector (x,y) — enough for PB 2D screen.
Structure pointf
x.l
y.l
EndStructure
; -------------------------------------------------------------------
; new(*v, x, y)
; FR: Initialise *v = (x,y).
; EN: Init *v = (x,y).
; -------------------------------------------------------------------
Declare new(*v.pointf, x.l, y.l)
; -------------------------------------------------------------------
; set(*from, *target)
; FR: Copie *from -> *target.
; EN: Copy *from -> *target.
; -------------------------------------------------------------------
Declare set(*from.pointf, *target.pointf)
; -------------------------------------------------------------------
; toString(*v) -> s
; FR: Représentation lisible "<x, y>".
; EN: Human-readable string "<x, y>".
; -------------------------------------------------------------------
Declare.s toString(*v.pointf)
; -------------------------------------------------------------------
; add(*out, *a, *b)
; FR: *out = *a + *b (composante par composante).
; EN: *out = *a + *b (component-wise).
; -------------------------------------------------------------------
Declare add(*vResult.pointf, *v1.pointf, *v2.pointf)
; -------------------------------------------------------------------
; diff(*out, *a, *b)
; FR: *out = *b - *a (vecteur AB).
; EN: *out = *b - *a (vector AB).
; -------------------------------------------------------------------
Declare diff(*vResult.pointf, *v1.pointf, *v2.pointf)
; -------------------------------------------------------------------
; dist(*a, *b) -> l
; FR: Distance Euclidienne |AB| arrondie (pour usage entier).
; EN: Euclidean distance |AB| rounded (integer use).
; -------------------------------------------------------------------
Declare.l dist(*v1.pointf, *v2.pointf)
; -------------------------------------------------------------------
; length(*v) -> l
; FR: Norme Euclidienne |v| arrondie.
; EN: Euclidean norm |v| rounded.
; -------------------------------------------------------------------
Declare.l length(*v.pointf)
; -------------------------------------------------------------------
; normalized(*out, *v)
; FR: Renvoie un *out unitaire approché (arrondi) — gardes contre 0.
; EN: Returns approximated unit vector *out (rounded) — zero-guarded.
; -------------------------------------------------------------------
Declare normalized(*vResult.pointf, *v.pointf)
EndDeclareModule
Module vector
EnableExplicit
Procedure new(*v.pointf, x.l, y.l) : *v\x = x : *v\y = y : EndProcedure
Procedure set(*from.pointf, *target.pointf)
*target\x = *from\x : *target\y = *from\y
EndProcedure
Procedure.s toString(*v.pointf)
ProcedureReturn "<" + Str(*v\x) + ", " + Str(*v\y) + ">"
EndProcedure
Procedure add(*vResult.pointf, *v1.pointf, *v2.pointf)
*vResult\x = *v1\x + *v2\x : *vResult\y = *v1\y + *v2\y
EndProcedure
Procedure diff(*vResult.pointf, *v1.pointf, *v2.pointf)
*vResult\x = *v2\x - *v1\x : *vResult\y = *v2\y - *v1\y
EndProcedure
Procedure.l dist(*v1.pointf, *v2.pointf)
Protected vResult.pointf
vResult\x = *v2\x - *v1\x : vResult\y = *v2\y - *v1\y
ProcedureReturn length(@vResult)
EndProcedure
Procedure.l length(*v.pointf)
ProcedureReturn Sqr(*v\x * *v\x + *v\y * *v\y)
EndProcedure
Procedure normalized(*vResult.pointf, *v.pointf)
Protected len.l = length(*v)
If len <> 0
*vResult\x = *v\x / len
*vResult\y = *v\y / len
Else
*vResult\x = 0 : *vResult\y = 0
EndIf
EndProcedure
EndModule
; =======================================================================
; Module: polygon
; =======================================================================
DeclareModule polygon
EnableExplicit
UseModule vector
; Const
#Epsilon = 0.01
; FR: Un polygone activable avec nom + liste de sommets (horaire).
; EN: Toggleable polygon with name + vertex list (clockwise).
Structure Polygon
List Polygon.pointf()
Name.s
Enabled.b
EndStructure
; ===================== API =====================
; addPoint(polygon(), x, y)
; FR: Ajoute un point (avec conversion Y de la démo).
; EN: Add a point (with demo Y transform).
Declare addPoint(List polygon.pointf(), x.l, y.l)
; distanceBetwenPoint(x1,y1,x2,y2) -> f
; FR: Distance Euclidienne (float).
; EN: Euclidean distance (float).
Declare.f distanceBetwenPoint(x1, y1, x2, y2)
; pointInside(*p, polygon()) -> b
; FR: Test point-in-polygon (ray casting).
; EN: Point-in-polygon (ray casting).
Declare pointInside(*p.pointf, List polygon.pointf())
; distanceToSegment(Px,Py, x1,y1, x2,y2) -> f
; FR: Distance point-segment robuste (clamp + epsilon).
; EN: Robust point-segment distance (clamp + epsilon).
Declare.f distanceToSegment(Px.l, Py.l, x1.l, y1.l, x2.l, y2.l)
; getClosestPointOnEdge(*out, *p, polygon())
; FR: Projeté de *p sur l’arête la plus proche du polygone.
; EN: Project *p onto the polygon’s nearest edge.
Declare getClosestPointOnEdge(*resultV.pointf, *p3.pointf, List polygon.pointf())
; isVertexConcave(polygon(), Index) -> b
; FR: Retourne #True si le sommet est concave (orientation).
; EN: Returns #True if vertex is concave (orientation).
Declare isVertexConcave(List polygon.pointf(), Index.i)
; nearestSegment(*p, polygon(), distMax=5) -> index
; FR: Index du segment le plus proche (ou -1).
; EN: Index of nearest segment (or -1).
Declare nearestSegment(*p.pointf, List polygon.pointf(), distMax = 5)
; inLineOfSight(*p1,*p2, polygon(), obstacle=#False) -> b
; FR: Visibilité entre *p1 et *p2 en tenant compte du polygone.
; EN: LoS between *p1 and *p2 using polygon.
Declare.b inLineOfSight(*p1.pointf, *p2.pointf, List polygon.pointf(), obstacle.b = #False)
; inLineOfSightGlobal(*p1,*p2, walkArea()) -> b
; FR: Visibilité globale (walk area + obstacles Enabled).
; EN: Global LoS (walk area + Enabled obstacles).
Declare.b inLineOfSightGlobal(*p1.pointf, *p2.pointf, List walkArea.Polygon())
; drawPoly(polygon(), lineColor, pointColor)
; FR/EN: Render helper for debug.
Declare drawPoly(List polygon.pointf(), lineColor = #White, pointColor = #White)
; SetMeta(*poly, label, enable=#True)
; FR/EN: Set name and enabled flag.
Declare SetMeta(*poly.Polygon, label.s, enable.b = #True)
; firstHitOnObstacles(*A,*B, walkArea(), *hit) -> b
; FR: Première intersection AB avec obstacles actifs.
; EN: First AB hit against active obstacles.
Declare.b firstHitOnObstacles(*A.pointf, *B.pointf, List walkArea.Polygon(), *hit.pointf)
; PushPerpFromNearestSegment(*pt, walk(), step=10, tol=24)
; FR: Recul perpendiculaire vers l’espace libre depuis le bord proche.
; EN: Perpendicular push away from nearest edge towards free space.
Declare PushPerpFromNearestSegment(*pt.vector::pointf, List walk.polygon::Polygon(), Stepi.f = 10.0, tol.f = 24.0)
EndDeclareModule
Module polygon
EnableExplicit
UseModule trace
UseModule vector
Procedure SetMeta(*poly.Polygon, label.s, enable.b = #True)
*poly\Name = label : *poly\Enabled = enable
EndProcedure
Procedure addPoint(List polygon.pointf(), x.l, y.l)
; NB demo: mapping en coordonnées écran
AddElement(polygon())
polygon()\x = x * 2
polygon()\y = y * 2 - 400
EndProcedure
Procedure.f distanceBetwenPoint(x1, y1, x2, y2)
ProcedureReturn Sqr((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))
EndProcedure
Procedure pointInside(*p.pointf, List polygon.pointf())
; FR: Test par comptage d’intersections (crossing number).
; EN: Ray cast / crossing number test.
Protected cn.l = 0
Define pa.pointf, pb.pointf
Protected i.l
For i = 0 To ListSize(polygon()) - 1
SelectElement(polygon(), i)
pa\x = polygon()\x : pa\y = polygon()\y
If i = ListSize(polygon()) - 1
SelectElement(polygon(), 0)
Else
SelectElement(polygon(), i + 1)
EndIf
pb\x = polygon()\x : pb\y = polygon()\y
If (((pa\y <= *p\y) And (pb\y > *p\y)) Or ((pa\y > *p\y) And (pb\y <= *p\y)))
Protected vt.f = (*p\y - pa\y) / (pb\y - pa\y)
If (*p\x < pa\x + vt * (pb\x - pa\x))
cn + 1
EndIf
EndIf
Next
ProcedureReturn (cn & 1)
EndProcedure
Procedure.f distanceToSegment(Px.l, Py.l, x1.l, y1.l, x2.l, y2.l)
Protected.f dx = x2 - x1, dy = y2 - y1
Protected.f denom = dx * dx + dy * dy
If denom <= #Epsilon
ProcedureReturn distanceBetwenPoint(Px, Py, x1, y1)
EndIf
Protected.f ratio = ((Px - x1) * dx + (Py - y1) * dy) / denom
If ratio < 0.0 - #Epsilon : ratio = 0.0
ElseIf ratio > 1.0 + #Epsilon : ratio = 1.0
EndIf
Protected.f qx = (1.0 - ratio) * x1 + ratio * x2
Protected.f qy = (1.0 - ratio) * y1 + ratio * y2
ProcedureReturn distanceBetwenPoint(Px, Py, qx, qy)
EndProcedure
Procedure getClosestPointOnEdge(*resultV.pointf, *p3.pointf, List polygon.pointf())
; FR: Renvoie dans *resultV le point du polygone (sur son arête la plus
; proche) qui est au plus près de *p3. Robuste cas dégénérés.
; EN: Writes into *resultV the closest point to *p3 lying on the nearest
; polygon edge. Robust to degenerate edges.
If ListSize(polygon()) < 2
vector::set(*p3, *resultV)
DBG("polygon", "getClosestPointOnEdge: polygon <2 points, no-op")
ProcedureReturn
EndIf
Protected.f tx = *p3\x, ty = *p3\y
Protected vi1 = -1, vi2 = -1
Protected.f mindist = 1.0e12
Protected.l ia, ib
For ia = 0 To ListSize(polygon()) - 1
Protected.pointf va, vb
SelectElement(polygon(), ia) : va\x = polygon()\x : va\y = polygon()\y
If ia = ListSize(polygon()) - 1 : ib = 0 : Else : ib = ia + 1 : EndIf
SelectElement(polygon(), ib) : vb\x = polygon()\x : vb\y = polygon()\y
Protected dist.f = distanceToSegment(tx, ty, va\x, va\y, vb\x, vb\y)
If dist < mindist
mindist = dist : vi1 = ia : vi2 = ib
EndIf
Next
If vi1 < 0 Or vi2 < 0
vector::set(*p3, *resultV)
DBG("polygon", "getClosestPointOnEdge: vi1/vi2 invalid, no-op")
ProcedureReturn
EndIf
Protected.f x1, y1, x2, y2, x3, y3
SelectElement(polygon(), vi1) : x1 = polygon()\x : y1 = polygon()\y
SelectElement(polygon(), vi2) : x2 = polygon()\x : y2 = polygon()\y
x3 = *p3\x : y3 = *p3\y
Protected.f dx = x2 - x1, dy = y2 - y1
Protected.f denom = dx * dx + dy * dy
If denom <= polygon::#Epsilon
Protected.f d1 = distanceBetwenPoint(x1, y1, x3, y3)
Protected.f d2 = distanceBetwenPoint(x2, y2, x3, y3)
If d1 <= d2 : vector::new(*resultV, x1, y1) : Else : vector::new(*resultV, x2, y2) : EndIf
ProcedureReturn
EndIf
Protected.f u = (((x3 - x1) * dx) + ((y3 - y1) * dy)) / denom
If u < 0.0 : u = 0.0 : ElseIf u > 1.0 : u = 1.0 : EndIf
vector::new(*resultV, x1 + u * dx, y1 + u * dy)
EndProcedure
Procedure isVertexConcave(List polygon.pointf(), Index.i)
; FR: Test de concavité via produit vectoriel.
; EN: Concavity test using cross product.
Protected.i nextIndex, prevIndex
Protected.f currentX, currentY, nextX, nextY, previousX, previousY
SelectElement(polygon(), Index)
currentX = polygon()\x : currentY = polygon()\y
nextIndex = Index + 1 : If nextIndex > ListSize(polygon()) - 1 : nextIndex = 0 : EndIf
SelectElement(polygon(), nextIndex)
nextX = polygon()\x : nextY = polygon()\y
prevIndex = Index - 1 : If prevIndex < 0 : prevIndex = ListSize(polygon()) - 1 : EndIf
SelectElement(polygon(), prevIndex)
previousX = polygon()\x : previousY = polygon()\y
Protected.f leftX = currentX - previousX
Protected.f leftY = currentY - previousY
Protected.f rightX = nextX - currentX
Protected.f rightY = nextY - currentY
Protected.f cross = (leftX * rightY) - (leftY * rightX)
ProcedureReturn Bool(cross < 0)
EndProcedure
Procedure Max(a, b) : If a > b : ProcedureReturn a : EndIf : ProcedureReturn b : EndProcedure
Procedure Min(a, b) : If a < b : ProcedureReturn a : EndIf : ProcedureReturn b : EndProcedure
Procedure nearestSegment(*p.pointf, List polygon.pointf(), distMax = 5)
; FR: Renvoie l’index du segment le plus proche si à <= distMax.
; EN: Returns the nearest segment index if within <= distMax.
Protected pa.pointf, pb.pointf
Protected segment.l = -1
Protected minDist.l = 4096
Protected i.l
For i = 0 To ListSize(polygon()) - 1
SelectElement(polygon(), i)
pa\x = polygon()\x : pa\y = polygon()\y
If i = ListSize(polygon()) - 1
SelectElement(polygon(), 0)
Else
SelectElement(polygon(), i + 1)
EndIf
pb\x = polygon()\x : pb\y = polygon()\y
Protected dist.l = distanceToSegment(*p\x, *p\y, pa\x, pa\y, pb\x, pb\y)
If dist <= distMax And dist < minDist
segment = ListIndex(polygon())
DBG("polygon", "Found segment: " + Str(segment))
minDist = dist
EndIf
Next
ProcedureReturn segment
EndProcedure
; -------------------------------------------------------------------
; LineIntersection(L1:[PA,PB], L2:[PA,PB], *Cross) -> 0/1/2
; FR: 1 si intersection sur les 2 segments, 0 sinon, 2 si droites
; sécantes mais en dehors des segments. *Cross rempli.
; EN: 1 if the segments intersect, 0 no, 2 if infinite lines cross
; but the hit is outside segments. *Cross filled.
; -------------------------------------------------------------------
Procedure LineIntersection(*L1PA.pointf, *L1PB.pointf, *L2PA.pointf, *L2PB.pointf, *Cross.pointf)
Protected.f A1, B1, C1, A2, B2, C2, det, tx, ty
A1 = *L1PB\y - *L1PA\y : B1 = *L1PA\x - *L1PB\x : C1 = A1 * *L1PA\x + B1 * *L1PA\y
A2 = *L2PB\y - *L2PA\y : B2 = *L2PA\x - *L2PB\x : C2 = A2 * *L2PA\x + B2 * *L2PA\y
det = A1 * B2 - A2 * B1
If Abs(det) <= #Epsilon
Protected.f dpa = Abs(A1 * *L2PA\x + B1 * *L2PA\y - C1)
Protected.f dpb = Abs(A1 * *L2PB\x + B1 * *L2PB\y - C1)
If dpa <= #Epsilon And dpb <= #Epsilon
Protected.f min1x = Min(*L1PA\x, *L1PB\x), max1x = Max(*L1PA\x, *L1PB\x)
Protected.f min2x = Min(*L2PA\x, *L2PB\x), max2x = Max(*L2PA\x, *L2PB\x)
Protected.f min1y = Min(*L1PA\y, *L1PB\y), max1y = Max(*L1PA\y, *L1PB\y)
Protected.f min2y = Min(*L2PA\y, *L2PB\y), max2y = Max(*L2PA\y, *L2PB\y)
If (Min(max1x, max2x) + #Epsilon >= Max(min1x, min2x)) And (Min(max1y, max2y) + #Epsilon >= Max(min1y, min2y))
*Cross\x = *L2PA\x : *Cross\y = *L2PA\y
ProcedureReturn 1
Else
ProcedureReturn 0
EndIf
EndIf
ProcedureReturn 0
EndIf
tx = (B2 * C1 - B1 * C2) / det
ty = (A1 * C2 - A2 * C1) / det
*Cross\x = tx : *Cross\y = ty
If tx < Min(*L1PA\x, *L1PB\x) - #Epsilon Or tx > Max(*L1PA\x, *L1PB\x) + #Epsilon Or ty < Min(*L1PA\y, *L1PB\y) - #Epsilon Or ty > Max(*L1PA\y, *L1PB\y) + #Epsilon
ProcedureReturn 2
EndIf
If tx < Min(*L2PA\x, *L2PB\x) - #Epsilon Or tx > Max(*L2PA\x, *L2PB\x) + #Epsilon Or ty < Min(*L2PA\y, *L2PB\y) - #Epsilon Or ty > Max(*L2PA\y, *L2PB\y) + #Epsilon
ProcedureReturn 2
EndIf
ProcedureReturn 1
EndProcedure
Procedure.b inLineOfSight(*p1.pointf, *p2.pointf, List polygon.pointf(), obstacle.b = #False)
; FR: LoS contre un polygone (edges bloquants) + correction pour
; cas de points sur sommets/segments (epsilon).
; EN: LoS against one polygon, robust near/at vertices & edges.
Protected.pointf tmp, tmp2, c, d, mid
vector::set(*p1, @tmp) : vector::set(*p2, @tmp2)
Protected.l i, ni
For i = 0 To ListSize(polygon()) - 1
SelectElement(polygon(), i) : vector::set(@polygon(), @c)
ni = i + 1 : If ni > ListSize(polygon()) - 1 : ni = 0 : EndIf
SelectElement(polygon(), ni) : vector::set(@polygon(), @d)
If (*p1\x = c\x And *p1\y = c\y And *p2\x = d\x And *p2\y = d\y) Or (*p1\x = d\x And *p1\y = d\y And *p2\x = c\x And *p2\y = c\y)
ProcedureReturn #True
EndIf
If (*p1\x = c\x And *p1\y = c\y And distanceToSegment(*p2\x, *p2\y, c\x, c\y, d\x, d\y) <= 3.0 + #Epsilon) Or
(*p1\x = d\x And *p1\y = d\y And distanceToSegment(*p2\x, *p2\y, c\x, c\y, d\x, d\y) <= 3.0 + #Epsilon)
ProcedureReturn #True
EndIf
Protected cross.pointf
If LineIntersection(@tmp, @tmp2, @c, @d, @cross) = 1
If distanceToSegment(tmp\x, tmp\y, c\x, c\y, d\x, d\y) > #Epsilon And distanceToSegment(tmp2\x, tmp2\y, c\x, c\y, d\x, d\y) > #Epsilon
ProcedureReturn #False
EndIf
EndIf
Next
vector::add(@mid, @tmp, @tmp2) : mid\x / 2 : mid\y / 2
Protected.b result = pointInside(@mid, polygon())
If obstacle : result = 1 - result : EndIf
ProcedureReturn result
EndProcedure
Procedure.b inLineOfSightGlobal(*p1.pointf, *p2.pointf, List walkArea.Polygon())
; FR: walkArea(0)=zone libre, autres=obstacles (#Enabled).
; EN: walkArea(0)=free area, others=obstacles (#Enabled).
Protected.b result = #False, obstacle = #False
ForEach walkArea()
If ListIndex(walkArea()) > 0
If walkArea()\Enabled = #False : Continue : EndIf
obstacle = #True
Else
obstacle = #False
EndIf
result = inLineOfSight(*p1, *p2, walkArea()\Polygon(), obstacle)
If result = #False : Break : EndIf
Next
ProcedureReturn result
EndProcedure
Procedure.b firstHitOnObstacles(*A.pointf, *B.pointf, List walkArea.Polygon(), *hit.pointf)
; FR: Première intersection du segment AB avec obstacles actifs.
; Retourne #True si trouvée et remplit *hit.
; EN: First hit of AB against active obstacles. Returns #True and
; fills *hit if found.
Protected.f bestT = 1.0e12
Protected found.b = #False
Protected.f ax = *A\x, ay = *A\y
Protected.f bx = *B\x, by = *B\y
Protected.f dx = bx - ax, dy = by - ay
Protected.f denom = dx * dx + dy * dy
If denom = 0 : ProcedureReturn #False : EndIf
Protected.pointf c, d, cross
Protected i.l, ni.l
ForEach walkArea()
If ListIndex(walkArea()) = 0 : Continue : EndIf
If walkArea()\Enabled = #False : Continue : EndIf
For i = 0 To ListSize(walkArea()\Polygon()) - 1
SelectElement(walkArea()\Polygon(), i) : c\x = walkArea()\Polygon()\x : c\y = walkArea()\Polygon()\y
ni = i + 1 : If ni > ListSize(walkArea()\Polygon()) - 1 : ni = 0 : EndIf
SelectElement(walkArea()\Polygon(), ni) : d\x = walkArea()\Polygon()\x : d\y = walkArea()\Polygon()\y
If LineIntersection(*A, *B, @c, @d, @cross) = 1
Protected.f t = ((cross\x - ax) * dx + (cross\y - ay) * dy) / denom
If t >= 0.0 And t <= 1.0 And t < bestT
bestT = t : *hit\x = cross\x : *hit\y = cross\y : found = #True
EndIf
EndIf
Next
Next
ProcedureReturn found
EndProcedure
; ------------------------------------------------------------
; IsFreeAt(x,y, walk()) -> b
; FR: Vrai si (x,y) est dans la zone walkArea(0) et hors obstacles
; Enabled. Préserve le curseur de liste.
; EN: True if (x,y) is inside walkArea(0) and outside Enabled obstacles.
; ------------------------------------------------------------
Procedure.b IsFreeAt(x.f, y.f, List walk.polygon::Polygon())
Protected pt.vector::pointf : pt\x = x : pt\y = y
Protected i.l, curIdx.l = ListIndex(walk())
If ListSize(walk()) = 0 : ProcedureReturn #False : EndIf
SelectElement(walk(), 0)
If polygon::pointInside(@pt, walk()\Polygon()) = #False
SelectElement(walk(), curIdx) : ProcedureReturn #False
EndIf
For i = 1 To ListSize(walk()) - 1
SelectElement(walk(), i)
If walk()\Enabled
If polygon::pointInside(@pt, walk()\Polygon())
SelectElement(walk(), curIdx) : ProcedureReturn #False
EndIf
EndIf
Next
SelectElement(walk(), curIdx)
ProcedureReturn #True
EndProcedure
; ------------------------------------------------------------
; PushPerpFromNearestSegment(*pt, walk(), step, tol)
; FR: Recul perpendiculaire depuis le segment le plus proche, en
; choisissant la normale qui mène vers l’espace libre (tests IsFreeAt).
; EN: Perpendicular push from nearest edge, choosing the normal that
; goes toward free space (IsFreeAt checks).
; ------------------------------------------------------------
Procedure PushPerpFromNearestSegment(*pt.vector::pointf, List walk.polygon::Polygon(), Stepi.f = 10.0, tol.f = 24.0)
Protected found.b = #False
Protected.f bestDist = 1.0e12
Protected.f ax, ay, bx, by, qx, qy, dx, dy, denom, u, d
Protected bestPolyIdx.l = -1, bestPbIdx.l = -1
ForEach walk()
If ListIndex(walk()) > 0 And walk()\Enabled = #False : Continue : EndIf
Protected seg = polygon::nearestSegment(*pt, walk()\Polygon(), tol)
If seg >= 0
Protected n = ListSize(walk()\Polygon())
Protected pbIndex = seg
Protected paIndex = pbIndex - 1 : If paIndex < 0 : paIndex = n - 1 : EndIf
SelectElement(walk()\Polygon(), paIndex) : ax = walk()\Polygon()\x : ay = walk()\Polygon()\y
SelectElement(walk()\Polygon(), pbIndex) : bx = walk()\Polygon()\x : by = walk()\Polygon()\y
d = polygon::distanceToSegment(*pt\x, *pt\y, ax, ay, bx, by)
If d < bestDist
bestDist = d : bestPolyIdx = ListIndex(walk()) : bestPbIdx = pbIndex : found = #True
EndIf
EndIf
Next
If found = #False : ProcedureReturn : EndIf
SelectElement(walk(), bestPolyIdx)
n = ListSize(walk()\Polygon())
pbIndex = bestPbIdx
paIndex = pbIndex - 1 : If paIndex < 0 : paIndex = n - 1 : EndIf
SelectElement(walk()\Polygon(), paIndex) : ax = walk()\Polygon()\x : ay = walk()\Polygon()\y
SelectElement(walk()\Polygon(), pbIndex) : bx = walk()\Polygon()\x : by = walk()\Polygon()\y
dx = bx - ax : dy = by - ay
denom = dx*dx + dy*dy
If denom <= polygon::#Epsilon
qx = ax : qy = ay
Else
u = ((*pt\x - ax) * dx + (*pt\y - ay) * dy) / denom
If u < 0.0 : u = 0.0 : ElseIf u > 1.0 : u = 1.0 : EndIf
qx = ax + u * dx : qy = ay + u * dy
EndIf
Protected.f nx1 = dy, ny1 = -dx
Protected.f len = Sqr(nx1*nx1 + ny1*ny1)
If len <= polygon::#Epsilon : ProcedureReturn : EndIf
nx1 / len : ny1 / len
Protected.f nx2 = -nx1, ny2 = -ny1
Protected probe.f = 2.0
Protected curIdx.l = ListIndex(walk())
Protected useNX.f, useNY.f
If IsFreeAt(qx + nx1 * probe, qy + ny1 * probe, walk())
useNX = nx1 : useNY = ny1
ElseIf IsFreeAt(qx + nx2 * probe, qy + ny2 * probe, walk())
useNX = nx2 : useNY = ny2
Else
useNX = *pt\x - qx : useNY = *pt\y - qy
len = Sqr(useNX*useNX + useNY*useNY)
If len <= polygon::#Epsilon : ProcedureReturn : EndIf
useNX / len : useNY / len
EndIf
SelectElement(walk(), curIdx)
Protected.f tryStep = Stepi
Protected.f tx = *pt\x + useNX * tryStep
Protected.f ty = *pt\y + useNY * tryStep
If IsFreeAt(tx, ty, walk()) = #False
Protected i
For i = 0 To 6
tryStep * 0.5
tx = *pt\x + useNX * tryStep
ty = *pt\y + useNY * tryStep
If IsFreeAt(tx, ty, walk()) Or tryStep < 0.5 : Break : EndIf
Next
EndIf
If IsFreeAt(tx, ty, walk())
*pt\x = tx : *pt\y = ty
EndIf
EndProcedure
Procedure drawPoly(List polygon.pointf(), lineColor = #White, pointColor = #White)
Protected pa.pointf, pb.pointf
Protected i.l
For i = 0 To ListSize(polygon()) - 1
SelectElement(polygon(), i)
pa\x = polygon()\x : pa\y = polygon()\y
If i = ListSize(polygon()) - 1
SelectElement(polygon(), 0)
Else
SelectElement(polygon(), i + 1)
EndIf
pb\x = polygon()\x : pb\y = polygon()\y
LineXY(pa\x, pa\y, pb\x, pb\y, lineColor)
Circle(pa\x, pa\y, 5, pointColor)
Next
EndProcedure
EndModule
; ======================================================================
; Module: scale — KNN(3) + Shepard IDW
; ======================================================================
DeclareModule scale
EnableExplicit
UseModule vector
UseModule trace
#EnableDebug = #True
#ScaleDefault = 1.0
#ScaleMin = 0.20
#ScaleMax = 4.00
#Epsilon = 0.0001
Structure Anchor
pos.vector::pointf
size.f
name.s
EndStructure
; AddScalePoint(x,y,size[,name])
Declare AddScalePoint(x.l, y.l, size.f, name.s = "")
; Clear()
Declare Clear()
; At(x,y) -> f
Declare.f At(x.l, y.l)
; Ease(current,target[,amount]) -> f
Declare.f Ease(current.f, target.f, amount.f = 0.15)
; draw([color])
Declare draw(color = #Yellow)
EndDeclareModule
Module scale
EnableExplicit
UseModule vector
UseModule trace
Global NewList gAnchors.Anchor()
Procedure AddScalePoint(x.l, y.l, size.f, name.s = "")
AddElement(gAnchors())
gAnchors()\pos\x = x : gAnchors()\pos\y = y
gAnchors()\size = size
gAnchors()\name = name
DBG("scale", "Add anchor '" + name + "' at (" + Str(x) + "," + Str(y) + ") size=" + StrF(size, 2))
EndProcedure
Procedure Clear()
ClearList(gAnchors())
DBG("scale", "Clear anchors")
EndProcedure
Procedure.f At(x.l, y.l)
; FR: Shepard IDW sur K=3 plus proches, clamp min/max.
; EN: Shepard IDW using the 3 nearest anchors, clamped.
If ListSize(gAnchors()) = 0 : ProcedureReturn #ScaleDefault : EndIf
If ListSize(gAnchors()) = 1 : FirstElement(gAnchors()) : ProcedureReturn gAnchors()\size : EndIf
Protected.f px = x, py = y
Protected.f bestD2_0 = 1.0e30, bestD2_1 = 1.0e30, bestD2_2 = 1.0e30
Protected.f bestS_0 = #ScaleDefault, bestS_1 = #ScaleDefault, bestS_2 = #ScaleDefault
Protected found0.b = #False, found1.b = #False, found2.b = #False
ForEach gAnchors()
Protected.f dx = px - gAnchors()\pos\x
Protected.f dy = py - gAnchors()\pos\y
Protected.f d2 = dx*dx + dy*dy
If d2 < #Epsilon : d2 = #Epsilon : EndIf
If d2 < bestD2_0
bestD2_2 = bestD2_1 : bestS_2 = bestS_1 : found2 = found1
bestD2_1 = bestD2_0 : bestS_1 = bestS_0 : found1 = found0
bestD2_0 = d2 : bestS_0 = gAnchors()\size : found0 = #True
ElseIf d2 < bestD2_1
bestD2_2 = bestD2_1 : bestS_2 = bestS_1 : found2 = found1
bestD2_1 = d2 : bestS_1 = gAnchors()\size : found1 = #True
ElseIf d2 < bestD2_2
bestD2_2 = d2 : bestS_2 = gAnchors()\size : found2 = #True
EndIf
Next
Protected.f num = 0.0, den = 0.0, w.f
If found0 : w = 1.0 / (bestD2_0 + #Epsilon) : num + w * bestS_0 : den + w : EndIf
If found1 : w = 1.0 / (bestD2_1 + #Epsilon) : num + w * bestS_1 : den + w : EndIf
If found2 : w = 1.0 / (bestD2_2 + #Epsilon) : num + w * bestS_2 : den + w : EndIf
Protected.f s : If den > 0.0 : s = num / den : Else : s = #ScaleDefault : EndIf
If #ScaleMin > 0.0 And s < #ScaleMin : s = #ScaleMin : EndIf
If #ScaleMax > 0.0 And s > #ScaleMax : s = #ScaleMax : EndIf
ProcedureReturn s
EndProcedure
Procedure.f Ease(current.f, target.f, amount.f = 0.15)
If amount <= 0.0 : amount = 0.15 : EndIf
If amount > 1.0 : amount = 1.0 : EndIf
ProcedureReturn current + (target - current) * amount
EndProcedure
Procedure draw(color = #Yellow)
ForEach gAnchors()
Circle(gAnchors()\pos\x, gAnchors()\pos\y, 4, color)
DrawText(gAnchors()\pos\x + 6, gAnchors()\pos\y - 6, StrF(gAnchors()\size, 2) + " " + gAnchors()\name, color)
Next
EndProcedure
EndModule
; =======================================================================
; Module: Pathfinding
; =======================================================================
DeclareModule Pathfinding
EnableExplicit
UseModule vector
; dijkstra(Graph(2), walkPath(), srcIndex, destIndex)
Declare dijkstra(Array graph.l(2), List walkPath.l(), srcIndex.i, destIndex.i)
; getWalkPath(*Start, *Target, walkArea(), walkPath())
Declare getWalkPath(*Start.vector::pointf, *Target.vector::pointf, List walkArea.polygon::Polygon(), List walkPath.vector::pointf())
; getPositionOnPath(walkPath(), Distance, *Position)
Declare getPositionOnPath(List walkPath.vector::pointf(), Distance.l, *Position.vector::pointf)
; drawPath(walkPath())
Declare drawPath(List walkPath.vector::pointf())
; getPathLength(walkPath()) -> l
Declare.l getPathLength(List walkPath.vector::pointf())
EndDeclareModule
Module Pathfinding
#EnableDebug=#True
EnableExplicit
UseModule vector
UseModule trace
#M = 999999
Procedure dijkstra(Array graph.l(2), List walkPath.l(), srcIndex.i, destIndex.i)
; FR: Dijkstra simple sur matrice d’adjacence (coûts entiers).
; EN: Basic Dijkstra on adjacency matrix (integer costs).
Protected Nb.l = ArraySize(graph(), 1)
Dim Distance(Nb - 1)
Dim Predecessor(Nb - 1)
Dim Mark(Nb - 1)
Protected z, MinK, MinD
For z = 0 To Nb - 1 : Mark(z) = #False : Next
Mark(srcIndex) = #True
Distance(srcIndex) = 0
Predecessor(srcIndex) = srcIndex
For z = 0 To Nb - 1
If z <> srcIndex
Distance(z) = graph(srcIndex, z)
Predecessor(z) = srcIndex
EndIf
Next
While Mark(destIndex) = #False
MinK = -1 : MinD = #M
For z = 0 To Nb - 1
If Mark(z) = #False
If Distance(z) < MinD : MinK = z : MinD = Distance(z) : EndIf
EndIf
Next
If MinD = #M
DBG("path", "No path between src and dest") : Break
ElseIf MinK = destIndex
DBG("path", "Walk path found") : Break
Else
Mark(MinK) = #True
EndIf
For z = 0 To Nb - 1
If Mark(z) = #False
If Distance(MinK) + graph(MinK, z) < Distance(z)
Distance(z) = Distance(MinK) + graph(MinK, z)
Predecessor(z) = MinK
EndIf
EndIf
Next
Wend
If MinK = destIndex
ClearList(walkPath())
AddElement(walkPath()) : walkPath() = destIndex
z = MinK
While Predecessor(z) <> srcIndex
FirstElement(walkPath()) : InsertElement(walkPath()) : walkPath() = Predecessor(z)
z = Predecessor(z)
Wend
FirstElement(walkPath()) : InsertElement(walkPath()) : walkPath() = Predecessor(z)
EndIf
EndProcedure
Procedure drawWalkPath(Array graph.l(2), List walkPointCoord.pointf())
; FR: Debug: affiche le graphe de visibilité.
; EN: Debug: draws visibility graph.
Protected pa.pointf, pb.pointf
Protected.l i, j, Nb = ArraySize(graph(), 1)
For i = 0 To Nb - 1
For j = 0 To Nb - 1
If graph(i, j) <> 0 And graph(i, j) <> #M
SelectElement(walkPointCoord(), i) : pa\x = walkPointCoord()\x : pa\y = walkPointCoord()\y
SelectElement(walkPointCoord(), j) : pb\x = walkPointCoord()\x : pb\y = walkPointCoord()\y
If polygon::distanceToSegment(MouseX(), MouseY(), pa\x, pa\y, pb\x, pb\y) < 1
LineXY(pa\x, pa\y, pb\x, pb\y, #Green)
Else
LineXY(pa\x, pa\y, pb\x, pb\y, #Gray)
EndIf
Circle(pa\x, pa\y, 5, #Yellow)
Circle(pb\x, pb\y, 5, #Yellow)
EndIf
Next
Next
EndProcedure
Procedure getWalkPath(*Start.pointf, *Target.pointf, List walkArea.polygon::Polygon(), List walkPath.pointf())
; FR: Construit un chemin Start->Target:
; 1) recule Start si collé à un bord,
; 2) projette Target sur le bord valide le plus proche si hors zone,
; 3) graphe de visibilité (sommets concaves/c onvexes),
; 4) Dijkstra,
; 5) fallback : nœud approché + point d’approche avant impact.
;
; EN: Build path Start->Target:
; 1) push Start if too close to an edge,
; 2) project Target to nearest valid edge if out of area,
; 3) visibility graph (concave/convex vertices),
; 4) Dijkstra,
; 5) fallback: closest reachable node + approach point before hit.
polygon::PushPerpFromNearestSegment(*Start, walkArea(), 1, 2)
Protected pa.pointf, pb.pointf, p.pointf
Protected i.l, j.l, k.l, pathIndex.l, nodeCount.l
Protected.b result, usedFallback = #False
NewList walkPointCoord.pointf()
NewList walkPointIndex.l()
Protected clicked.pointf
clicked\x = *Target\x : clicked\y = *Target\y
Protected best.pointf
Protected.f bestDist = 1.0e12
Protected bestFound.b = #False
ForEach walkArea()
If ListIndex(walkArea()) > 0 And walkArea()\Enabled = #False : Continue : EndIf
result = polygon::pointInside(@clicked, walkArea()\Polygon())
If ListIndex(walkArea()) = 0 : result = 1 - result : EndIf
If result = #True
polygon::getClosestPointOnEdge(@p, @clicked, walkArea()\Polygon())
Protected.f d = polygon::distanceBetwenPoint(clicked\x, clicked\y, p\x, p\y)
If d < bestDist
bestDist = d : bestFound = #True : best\x = p\x : best\y = p\y
EndIf
EndIf
Next
If bestFound
*Target\x = best\x : *Target\y = best\y
EndIf
AddElement(walkPointCoord()) : walkPointCoord()\x = *Start\x : walkPointCoord()\y = *Start\y
AddElement(walkPointCoord()) : walkPointCoord()\x = *Target\x : walkPointCoord()\y = *Target\y
If polygon::inLineOfSightGlobal(*Start, *Target, walkArea())
Dim Graph.l(1, 1)
Graph(0, 0) = 0 : Graph(1, 1) = 0
Graph(0, 1) = polygon::distanceBetwenPoint(*Start\x, *Start\y, *Target\x, *Target\y)
Graph(1, 0) = Graph(0, 1)
drawWalkPath(Graph(), walkPointCoord())
ClearList(walkPath())
AddElement(walkPath()) : walkPath()\x = *Start\x : walkPath()\y = *Start\y
AddElement(walkPath()) : walkPath()\x = *Target\x : walkPath()\y = *Target\y
ProcedureReturn
EndIf
ForEach walkArea()
If ListIndex(walkArea()) > 0 And walkArea()\Enabled = #False : Continue : EndIf
For i = 0 To ListSize(walkArea()\Polygon()) - 1
SelectElement(walkArea()\Polygon(), i)
pa\x = walkArea()\Polygon()\x : pa\y = walkArea()\Polygon()\y
If ListIndex(walkArea()) = 0
If polygon::isVertexConcave(walkArea()\Polygon(), i) = #True
AddElement(walkPointCoord()) : walkPointCoord()\x = pa\x : walkPointCoord()\y = pa\y
EndIf
Else
If polygon::isVertexConcave(walkArea()\Polygon(), i) = #False
AddElement(walkPointCoord()) : walkPointCoord()\x = pa\x : walkPointCoord()\y = pa\y
EndIf
EndIf
Next
Next
nodeCount = ListSize(walkPointCoord())
Dim Graph.l(nodeCount, nodeCount)
For i = 0 To nodeCount - 1
SelectElement(walkPointCoord(), i) : pa\x = walkPointCoord()\x : pa\y = walkPointCoord()\y
For j = 0 To nodeCount - 1
If i <> j
SelectElement(walkPointCoord(), j) : pb\x = walkPointCoord()\x : pb\y = walkPointCoord()\y
If polygon::inLineOfSightGlobal(@pa, @pb, walkArea()) = #True
Graph(i, j) = polygon::distanceBetwenPoint(pa\x, pa\y, pb\x, pb\y)
Else
Graph(i, j) = 999999
EndIf
Else
Graph(i, j) = 0
EndIf
Next
Next
Pathfinding::dijkstra(Graph(), walkPointIndex(), 0, 1)
drawWalkPath(Graph(), walkPointCoord())
If ListSize(walkPointIndex()) = 0
usedFallback = #True
DBG("path", "Fallback: cible bloquée, recherche du nœud atteignable le plus proche")
Protected.f bestScore = 1.0e12
Protected bestFound2.b = #False
NewList bestPath.l()
For k = 0 To nodeCount - 1
NewList tmpPath.l()
Pathfinding::dijkstra(Graph(), tmpPath(), 0, k)
If ListSize(tmpPath()) > 0
SelectElement(walkPointCoord(), k)
Protected.f score = polygon::distanceBetwenPoint(walkPointCoord()\x, walkPointCoord()\y, *Target\x, *Target\y)
If score < bestScore
bestScore = score : bestFound2 = #True
ClearList(bestPath())
ForEach tmpPath() : AddElement(bestPath()) : bestPath() = tmpPath() : Next
EndIf
EndIf
Next
If bestFound2
ClearList(walkPointIndex())
ForEach bestPath() : AddElement(walkPointIndex()) : walkPointIndex() = bestPath() : Next
EndIf
EndIf
ClearList(walkPath())
For pathIndex = 0 To ListSize(walkPointIndex()) - 1
SelectElement(walkPointIndex(), pathIndex)
SelectElement(walkPointCoord(), walkPointIndex())
AddElement(walkPath())
walkPath()\x = walkPointCoord()\x : walkPath()\y = walkPointCoord()\y
Next
If usedFallback And ListSize(walkPath()) > 0
LastElement(walkPath()) : pa\x = walkPath()\x : pa\y = walkPath()\y
Protected hit.pointf
If polygon::firstHitOnObstacles(@pa, *Target, walkArea(), @hit)
Protected.f approachMargin = 4.0
Protected.f dx = *Target\x - pa\x, dy = *Target\y - pa\y
Protected.f len = Sqr(dx * dx + dy * dy)
If len <> 0 : dx / len : dy / len : EndIf
Protected approach.pointf
approach\x = hit\x - dx * approachMargin
approach\y = hit\y - dy * approachMargin
AddElement(walkPath()) : walkPath()\x = approach\x : walkPath()\y = approach\y
DBG("path", "Ajout point d’approche (" + Str(Int(approach\x)) + "," + Str(Int(approach\y)) + ")")
EndIf
EndIf
EndProcedure
Procedure getPositionOnPath(List walkPath.pointf(), Distance.l, *Position.pointf)
; FR: Place *Position à une Distance le long du chemin (linéaire),
; ignore segments de longueur ~0 pour éviter div/0.
; EN: Places *Position at a Distance along the path (linear),
; skipping near-zero segments to avoid div/0.
Protected n.l
Protected.l x1, y1, x2, y2
Protected.f segLen, pathLenSoFar.f, oldPathLen.f, segDist.f
If ListSize(walkPath()) = 0 : ProcedureReturn : EndIf
SelectElement(walkPath(), 0)
*Position\x = walkPath()\x : *Position\y = walkPath()\y
pathLenSoFar = 0.0
For n = 0 To ListSize(walkPath()) - 1
SelectElement(walkPath(), n)
x1 = walkPath()\x : y1 = walkPath()\y
If SelectElement(walkPath(), n + 1)
x2 = walkPath()\x : y2 = walkPath()\y
oldPathLen = pathLenSoFar
segLen = polygon::distanceBetwenPoint(x1, y1, x2, y2)
If segLen <= polygon::#Epsilon : Continue : EndIf
pathLenSoFar + segLen
If Distance >= oldPathLen And Distance <= pathLenSoFar
segDist = Distance - oldPathLen
*Position\x = x1 + (segDist / segLen) * (x2 - x1)
*Position\y = y1 + (segDist / segLen) * (y2 - y1)
Break
EndIf
EndIf
Next
EndProcedure
Procedure drawPath(List walkPath.pointf())
; FR/EN: Debug draw of the current path.
Protected.l x1, y1, x2, y2, hypothenus, n
For n = 0 To ListSize(walkPath()) - 1
SelectElement(walkPath(), n)
x1 = walkPath()\x : y1 = walkPath()\y
If SelectElement(walkPath(), n + 1)
x2 = walkPath()\x : y2 = walkPath()\y
hypothenus = polygon::distanceBetwenPoint(x1, y1, x2, y2)
LineXY(x1, y1, x2, y2, #White)
Circle(x1, y1, 5, #Red)
Circle(x2, y2, 5, #Red)
EndIf
Next
EndProcedure
Procedure.l getPathLength(List walkPath.pointf())
; FR/EN: Sum of segment lengths (integer).
Protected.l PathDistance = 0
Protected.l x1, y1, x2, y2, hypothenus, n
For n = 0 To ListSize(walkPath()) - 1
SelectElement(walkPath(), n)
x1 = walkPath()\x : y1 = walkPath()\y
If SelectElement(walkPath(), n + 1)
x2 = walkPath()\x : y2 = walkPath()\y
hypothenus = polygon::distanceBetwenPoint(x1, y1, x2, y2)
PathDistance + hypothenus
EndIf
Next
ProcedureReturn PathDistance
EndProcedure
EndModule
; =======================================================================
;- Demo / Test harness
; =======================================================================
Structure room
List walkArea.polygon::Polygon()
EndStructure
Structure chara
coord.vector::pointf
target.vector::pointf
List Path.vector::pointf()
pathLength.l
move.b
EndStructure
Define mainChara.chara
mainChara\coord\x = 260 : mainChara\coord\y = 120
Define room.room
; Walkable area (index 0)
AddElement(room\walkArea())
polygon::SetMeta(@room\walkArea(), "Walk Area", #True)
polygon::addPoint(room\walkArea()\Polygon(), 5, 248)
polygon::addPoint(room\walkArea()\Polygon(), 5, 248) ; (dup pour la forme de la démo)
polygon::addPoint(room\walkArea()\Polygon(), 235, 248)
polygon::addPoint(room\walkArea()\Polygon(), 252, 277)
polygon::addPoint(room\walkArea()\Polygon(), 214, 283)
polygon::addPoint(room\walkArea()\Polygon(), 217, 300)
polygon::addPoint(room\walkArea()\Polygon(), 235, 319)
polygon::addPoint(room\walkArea()\Polygon(), 265, 339)
polygon::addPoint(room\walkArea()\Polygon(), 275, 352)
polygon::addPoint(room\walkArea()\Polygon(), 310, 350)
polygon::addPoint(room\walkArea()\Polygon(), 309, 312)
polygon::addPoint(room\walkArea()\Polygon(), 322, 308)
polygon::addPoint(room\walkArea()\Polygon(), 304, 279)
polygon::addPoint(room\walkArea()\Polygon(), 307, 249)
polygon::addPoint(room\walkArea()\Polygon(), 419, 248)
polygon::addPoint(room\walkArea()\Polygon(), 431, 262)
polygon::addPoint(room\walkArea()\Polygon(), 389, 274)
polygon::addPoint(room\walkArea()\Polygon(), 378, 295)
polygon::addPoint(room\walkArea()\Polygon(), 408, 311)
polygon::addPoint(room\walkArea()\Polygon(), 397, 316)
polygon::addPoint(room\walkArea()\Polygon(), 378, 309)
polygon::addPoint(room\walkArea()\Polygon(), 365, 323)
polygon::addPoint(room\walkArea()\Polygon(), 342, 360)
polygon::addPoint(room\walkArea()\Polygon(), 358, 379)
polygon::addPoint(room\walkArea()\Polygon(), 205, 379)
polygon::addPoint(room\walkArea()\Polygon(), 206, 338)
polygon::addPoint(room\walkArea()\Polygon(), 212, 320)
polygon::addPoint(room\walkArea()\Polygon(), 198, 316)
polygon::addPoint(room\walkArea()\Polygon(), 162, 298)
polygon::addPoint(room\walkArea()\Polygon(), 119, 305)
polygon::addPoint(room\walkArea()\Polygon(), 99, 338)
polygon::addPoint(room\walkArea()\Polygon(), 91, 362)
polygon::addPoint(room\walkArea()\Polygon(), 79, 372)
polygon::addPoint(room\walkArea()\Polygon(), 90, 380)
polygon::addPoint(room\walkArea()\Polygon(), 4, 379)
; Obstacle #1
AddElement(room\walkArea())
polygon::SetMeta(@room\walkArea(), "Bloc A", #True)
polygon::addPoint(room\walkArea()\Polygon(), 120, 280)
polygon::addPoint(room\walkArea()\Polygon(), 120, 260)
polygon::addPoint(room\walkArea()\Polygon(), 140, 260)
polygon::addPoint(room\walkArea()\Polygon(), 140, 280)
; Obstacle #2 (circle)
AddElement(room\walkArea())
polygon::SetMeta(@room\walkArea(), "Obstacle Cercle", #True)
Define z
For z = 0 To 360 Step 36
polygon::addPoint(room\walkArea()\Polygon(), 60 + 20 * Cos(Radian(z)), 300 + 20 * Sin(Radian(z)))
Next
; Obstacle #3 (vertical wall)
AddElement(room\walkArea())
polygon::SetMeta(@room\walkArea(), "Mur vertical", #True)
polygon::addPoint(room\walkArea()\Polygon(), 220, 50)
polygon::addPoint(room\walkArea()\Polygon(), 220, 400)
polygon::addPoint(room\walkArea()\Polygon(), 225, 400)
polygon::addPoint(room\walkArea()\Polygon(), 225, 50)
Define *wall.polygon::Polygon = @room\walkArea()
;-scale (depth anchors)
scale::AddScalePoint( 80, 80, 1, "Test")
scale::AddScalePoint(440, 120, 1, "Test")
scale::AddScalePoint(640, 260, 0.25, "Test")
scale::AddScalePoint(330, 200, 1, "Test")
scale::AddScalePoint(200, 360, 1, "Test")
; -----------------------------------------------------------------------
; PB 2D init
; -----------------------------------------------------------------------
If InitSprite()
If InitKeyboard() And InitMouse()
Define winMain = OpenWindow(#PB_Any, 0, 0, 1024, 800, "Press [Esc] to close", #PB_Window_ScreenCentered | #PB_Window_SystemMenu)
OpenWindowedScreen(WindowID(winMain), 0, 0, 1024, 800, 1, 0, 0)
Else
MessageRequester("Error", "Unable to init keyboard or mouse") : End
EndIf
Else
MessageRequester("Error", "Unable to init sprite") : End
EndIf
; -----------------------------------------------------------------------
; Main loop (+ F1 debounce to toggle wall)
; -----------------------------------------------------------------------
Define EventID.l, Distance.l
Define f1Latch.b = #False
Repeat
Delay(1)
Repeat : Until WindowEvent() = 0
ExamineKeyboard() : ExamineMouse()
ClearScreen(0)
StartDrawing(ScreenOutput())
mainChara\target\x = MouseX()
mainChara\target\y = MouseY()
; Draw polygons (grey if disabled)
ForEach room\walkArea()
Define color
If ListIndex(room\walkArea()) = 0
color = RGB(0, 0, 255)
ElseIf room\walkArea()\Enabled
color = RGB(100, 100, 255)
Else
color = RGB(80, 80, 80)
EndIf
polygon::drawPoly(room\walkArea()\Polygon(), color)
Next
; Left click: compute path
If MouseButton(#PB_MouseButton_Left)
Pathfinding::getWalkPath(@mainChara\coord, @mainChara\target, room\walkArea(), mainChara\Path())
mainChara\move = #True
mainChara\pathLength = Pathfinding::getPathLength(mainChara\Path())
Distance = 0
EndIf
; Right click: (hook dispo si besoin)
If MouseButton(#PB_MouseButton_Right)
; reserved for alternate action
EndIf
; Move along the path
If mainChara\move = #True
Pathfinding::getPositionOnPath(mainChara\Path(), Distance, @mainChara\coord)
Distance + 5
If Distance >= mainChara\pathLength
mainChara\move = #False : Distance = 0
EndIf
EndIf
; Draw current path
Pathfinding::drawPath(mainChara\Path())
; Size from depth anchors
Define s.f = scale::At(mainChara\coord\x, mainChara\coord\y)
Circle(mainChara\coord\x, mainChara\coord\y, 10 * s, #Green)
DrawText(150, 20, "taille: " + StrF(s))
scale::draw(#Yellow)
DrawText(20, 0, " Chara : " + Str(mainChara\coord\x) + "," + Str(mainChara\coord\y))
DrawText(20, 20, "Distance: " + Str(Distance))
DrawText(20, 40, "F1 - Toggle Obstacle")
; F1 edge: toggle Enabled
If KeyboardPushed(#PB_Key_F1) And f1Latch = #False
f1Latch = #True
If *wall
*wall\Enabled = 1 - *wall\Enabled
trace::DBG("demo", "Mur vertical Enabled=" + Str(*wall\Enabled))
EndIf
EndIf
If KeyboardPushed(#PB_Key_F1) = 0 : f1Latch = #False : EndIf
DrawingMode(#PB_2DDrawing_XOr)
Line(MouseX() - 1, MouseY(), -10, 1, #White)
Line(MouseX() + 1, MouseY(), 10, 1, #White)
Line(MouseX(), MouseY() - 1, 1, -10, #White)
Line(MouseX(), MouseY() + 1, 1, 10, #White)
DrawingMode(#PB_2DDrawing_Default)
StopDrawing()
FlipBuffers()
Until KeyboardReleased(#PB_Key_Escape) Or EventID = #PB_Event_CloseWindow