Ooops
January 25, 2022, 07:04:00

Author Topic: Jigsaw Pieces  (Read 3049 times)

Offline therevills

  • Hero Member
  • *****
  • Posts: 729
Re: Jigsaw Pieces
« Reply #75 on: January 02, 2022, 09:38:39 »
Yeah it's totally in WIP  ;D

At the moment the active piece smoothly rotates, whereas the the connected pieces snap.

Offline therevills

  • Hero Member
  • *****
  • Posts: 729
Re: Jigsaw Pieces
« Reply #76 on: January 03, 2022, 01:04:15 »
Progress??? Maybe, maybe not  :P

Offline therevills

  • Hero Member
  • *****
  • Posts: 729
Re: Jigsaw Pieces
« Reply #77 on: January 03, 2022, 04:37:31 »
Getting there... not sure what's wrong now with my handle offsets  ???

Code: [Select]
' J I G S A W - G A M E   E X A M P L E
'

' Copyright: Public Domain
 
SuperStrict
Import brl.Retro
Import brl.LinkedList
Import brl.GLMax2D
Import brl.PNGLoader

'these are included at the bottom of the code
'Include "imagehelper.bmx"
'Include "cameraclass.bmx"

Global Move:Int
 
'Delevopers Flags:
Const SORTED:Int = 1  ' set to 1 to get sorted JigSaw pieces during Development/Debugging
Const ONLY_MASK:Int = 0  ' set to 1 to see only JigSaw piece masks during Development/Debugging
Const SHOW_TYP:Int = 0  ' set to 1 to see additional Typ information (like "FHHW" )next to the piece during Development/Debugging
Const LIGHT_OFF:Int = 0  ' set to 1 to switch off border lighting during Development/Debugging
Const ONLY_BLUE:Int = 0  ' set to 1 to switch off pixel picking from JigFoto during Development/Debugging
Const NO_SHADOW:Int = 1  ' set to 1 to hide shadow layer during Development/Debugging
Const NO_OUTLINE:Int = 1
Const SHOW_GRID:Int = 0  ' set to 1 to show placement grid for tiles and 2 to show puzzle outline
Const SHOW_TILE_ORIGIN:Int = 1  ' set to 1 to handle of the tiles
Const SHOW_ID:Int = 1
 
AppTitle = "JigSaw"
Const SCREEN_WIDTH:Int = 1400
Const SCREEN_HEIGHT:Int = 900

Graphics SCREEN_WIDTH, SCREEN_HEIGHT

Global Camera:TCamera = TCamera.Create(SCREEN_WIDTH, SCREEN_HEIGHT)
Global TargetX:Float = SCREEN_WIDTH / 2, TargetY:Float = SCREEN_HEIGHT / 2
Camera.SetXY(TargetX, TargetY)
Global displayCameraInfo:Int = False

Global jigsaw:TJigsaw = New TJigsaw
jigsaw.Init()
 
SetClsColor 55, 111, 111
Repeat
Cls
SetColor(255, 255, 255)
SetBlend(ALPHABLEND)

jigsaw.Update(Camera)
MouseAction()
Camera.Start(TargetX, TargetY, False)
jigsaw.Draw()
Camera.Stop()

If jigsaw.selectedTile
DrawText("Selected Tile ID: " + jigsaw.selectedTile.id, 10, 10)
Else
DrawText("Selected Tile ID: NULL", 10, 10)
EndIf

Flip(1)
Until AppTerminate()

Function TweenSmooth:Float(p1:Float, p2:Float, t:Float)
Local v:Float = SmoothStep(t)
Return p1 + v * (p2 - p1)
End Function

Function SmoothStep:Float(x:Float, interpSmooth:Int = 1)
For Local i:Int = 0 Until interpSmooth
x:*x * (3 - 2 * x)
Next
Return x
End Function

Function Distance:Float(x1:Float, y1:Float, x2:Float, y2:Float)
Local dx:Float = x1 - x2
Local dy:Float = y1 - y2
Return Sqr((dx ^ 2) + (dy ^ 2))
End Function
 

Function MoveConnectedPieces(piece:TJigsawTile, connectedPieces:TJigsawTile[], doneMap:TMap)

For Local i:Int = 0 Until connectedPieces.Length

If connectedPieces[i]
Local pc:TJigsawTile = connectedPieces[i]
If pc <> piece And Not doneMap.Contains(pc.id)
Local tx:Float
Local ty:Float
Select i
Case TJigsaw.ABOVE_PIECE
Select piece.rotation
Case 0
tx = piece.x
ty = piece.y - jigsaw.tileConfig.baseSize
Case 90
tx = piece.x + jigsaw.tileConfig.baseSize
ty = piece.y
Case 180
tx = piece.x
ty = piece.y + jigsaw.tileConfig.baseSize
Case 270
tx = piece.x - jigsaw.tileConfig.baseSize
ty = piece.y
End Select
Case TJigsaw.BELOW_PIECE
Select piece.rotation
Case 0
tx = piece.x
ty = piece.y + jigsaw.tileConfig.baseSize
Case 90
tx = piece.x - jigsaw.tileConfig.baseSize
ty = piece.y
Case 180
tx = piece.x
ty = piece.y - jigsaw.tileConfig.baseSize
Case 270
tx = piece.x + jigsaw.tileConfig.baseSize
ty = piece.y
End Select
Case TJigsaw.LEFT_PIECE
Select piece.rotation
Case 0
tx = piece.x + jigsaw.tileConfig.baseSize
ty = piece.y
Case 90
tx = piece.x
ty = piece.y + jigsaw.tileConfig.baseSize
Case 180
tx = piece.x - jigsaw.tileConfig.baseSize
ty = piece.y
Case 270
tx = piece.x
ty = piece.y - jigsaw.tileConfig.baseSize
End Select
Case TJigsaw.RIGHT_PIECE
Select piece.rotation
Case 0
tx = piece.x - jigsaw.tileConfig.baseSize
ty = piece.y
Case 90
tx = piece.x
ty = piece.y - jigsaw.tileConfig.baseSize
Case 180
tx = piece.x + jigsaw.tileConfig.baseSize
ty = piece.y
Case 270
tx = piece.x
ty = piece.y + jigsaw.tileConfig.baseSize
End Select

End Select
pc.x = tx
pc.y = ty
pc.rotation = piece.rotation
doneMap.Insert(piece.id, piece)
MoveConnectedPieces(pc, pc.connectedPieces, doneMap)
EndIf
End If
Next
End Function

Function MouseAction()
If KeyHit(KEY_F1)
displayCameraInfo = Not displayCameraInfo
Camera.SetDisplayInfo(displayCameraInfo)
End If

If MouseDown(1)
If Move = 0
Local oldSelectedTile:TJigsawTile = jigsaw.selectedTile
jigsaw.SelectTile(jigsaw.GetTileAtXY(Camera.MouseX() - jigsaw.x, Camera.MouseY() - jigsaw.y))
If jigsaw.selectedTile
If jigsaw.selectedTile <> oldSelectedTile
jigsaw.DragTile(jigsaw.selectedTile)
jigsaw.HoverTile(jigsaw.selectedTile)
EndIf
Move = 1
EndIf
ElseIf Move = 1
Local piece:TJigsawTile = jigsaw.selectedTile
If piece
piece.x = Camera.MouseX()
piece.y = Camera.MouseY()

Local doneMap:TMap = New TMap
MoveConnectedPieces(piece, piece.connectedPieces, doneMap)
EndIf
EndIf
Else
If jigsaw.selectedTile
If jigsaw.TryToPlaceTile(jigsaw.selectedTile)
jigsaw.selectedTile = Null
End If
jigsaw.DropTile(jigsaw.selectedTile)
EndIf
Move = 0
EndIf


If MouseHit(2) ' rotation
jigsaw.SelectTile(jigsaw.GetTileAtXY(Camera.MouseX() - jigsaw.x, Camera.MouseY() - jigsaw.y))
Local piece:TJigsawTile = jigsaw.selectedTile
If piece And Not piece.startRotationTween
jigsaw.selectedTile.SetUpRotation()
EndIf
EndIf


Local mxs:Float = MouseXSpeed() * Camera.zoom
Local mys:Float = MouseYSpeed() * Camera.zoom
If MouseDown(2)
TargetX:-mxs
TargetY:-mys
' limit target x y to camera limits
Camera.SetXY(TargetX, TargetY)
TargetX = -Camera.x
TargetY = -Camera.y
EndIf


Local mzs:Int = MouseZSpeed()
If mzs > 0 Or KeyDown(KEY_Q)
Camera.targetZoom:+Camera.zoomSpeed * 2
EndIf
If mzs < 0 Or KeyDown(KEY_E)
Camera.targetZoom:-Camera.zoomSpeed * 2
EndIf
End Function



Type TJigsawTileConfig
Field size:Int
Field baseSize:Int
Field borderSize:Int

Field basePixShape:TPixmap
Field basePixNoseShape:TPixmap
End Type


Type TJigsawTile
Const outlineOffset:Int = 0
Const shadowBlurStrength:Float = 0.25
Const shadowAlpha:Float = 0.25
Const draggedShadowOffset:Int = 8
Const draggedShadowAlpha:Float = 0.4
Const selectedOffset:Int = -2

Field id:String = ""
Field sideTypes:Int[]
Field sideOffsets:Int[]
Field x:Int, y:Int
Field ox:Int, oy:Int
Field startMoveTween:Int = False
Field tx:Int, ty:Int
Field moveTweenTimer:Float = 0
Field moveTweenSpeedTimer:Float = 0.01

Field rotation:Int
Field startRotationTween:Int = False, rotationTweenTimer:Float = 0, rotationTweenSpeedTimer:Float = 0.03, startRotation:Int, targetRotation:Int
Field column:Int, row:Int
Field tileConfig:TJigsawTileConfig
Field placed:Int
Field selected:Int
Field shadowOffsetX:Float, shadowOffsetY:Float, maxShadowOffset:Int = 8
Field shadowSpeed:Float = 0.4
Field pixTile:TPixmap, pixShape:TPixmap
Field imgTile:TImage, imgShadow:TImage, imgOutline:TImage, imgGridBackground:TImage
Field scaleX:Float = 1, scaleY:Float = 1

    Field checkPosition:Int = False

Field connectedPieces:TJigsawTile[4]
Field neighbourPieces:TJigsawTile[4]
Field originalHandleX:Int, originalHandleY:Int

   
Method Init:TJigsawTile(id:String, jigsawPicture:TPixmap, tileConfig:TJigsawTileConfig, sideTypes:Int[], sideOffsets:Int[], column:Int, row:Int)
Self.id = id
Self.column = column
Self.row = row
Self.sideTypes = sideTypes
Self.sideOffsets = sideOffsets
Self.tileConfig = tileConfig

Self._GenerateShape()
Self._GenerateTileImage(jigsawPicture)
If Not NO_SHADOW Then Self._GenerateShadowImage()
If Not NO_OUTLINE Then Self._GenerateOutlineImage()
If SHOW_GRID = 2 Then Self._GenerateGridBackgroundImage()


MidHandleImage(imgTile)
originalHandleX = imgTile.handle_x
originalHandleY = imgTile.handle_y

If Not NO_SHADOW Then MidHandleImage(imgShadow)
If Not NO_OUTLINE Then MidHandleImage(imgOutline)

Return Self
End Method



Method DrawShadow(offsetX:Int, offsetY:Int)
If NO_SHADOW Then Return
If imgShadow
Local oldA:Float = GetAlpha()

SetRotation(rotation)

If selected
SetAlpha(draggedShadowAlpha * oldA)
DrawImage(imgShadow, x + offsetX + draggedShadowOffset + selectedOffset, y + offsetY + draggedShadowOffset + selectedOffset)
Else
SetAlpha(shadowAlpha * oldA)
DrawImage(imgShadow, x + offsetX + shadowOffsetX, y + offsetY + shadowOffsetY)
EndIf

SetRotation(0)
SetAlpha(oldA)
EndIf
End Method


Method DrawOutline(offsetX:Int, offsetY:Int)
If imgOutline
SetRotation(rotation)
If selected
DrawImage(imgOutline, x + offsetX + outlineOffset + selectedOffset, y + offsetY + outlineOffset + selectedOffset)
Else
DrawImage(imgOutline, x + offsetX + outlineOffset, y + offsetY + outlineOffset)
EndIf
SetRotation(0)
EndIf
End Method

Method SetUpMove(xx:Float, yy:Float)
Self.startMoveTween = True
Self.ox = Self.x
Self.oy = Self.y
Self.tx = xx
Self.ty = yy
Self.moveTweenTimer = 0
End Method

Method SetUpRotationConnected(startPiece:TJigsawTile, nextPiece:TJigsawTile, doneMap:TMap)
Print "SetUpRotationConnected " + nextPiece.id
For Local i:Int = 0 Until nextPiece.connectedPieces.Length

If nextPiece.connectedPieces[i]

Local piece:TJigsawTile = nextPiece.connectedPieces[i]
If Not doneMap.Contains(piece.id)

Local w2:Float = piece.imgTile.width / 2
Local h2:Float = piece.imgTile.height / 2

Local xDiff:Float = startPiece.x - piece.x
Local yDiff:Float = startPiece.y - piece.y

Print "piece.id = " + piece.id
Print "oldhandle: " + piece.imgTile.handle_x + " , " + piece.imgTile.handle_y
Select i
Case TJigsaw.ABOVE_PIECE
Print "above " + targetRotation

Select targetRotation
Case 360
piece.imgTile.handle_x = yDiff + h2
piece.imgTile.handle_y = xDiff + w2
Case 90
piece.imgTile.handle_x = xDiff + w2
piece.imgTile.handle_y = yDiff + h2
Case 180
piece.imgTile.handle_x = yDiff + h2
piece.imgTile.handle_y = -xDiff + w2
Case 270
piece.imgTile.handle_x = xDiff + w2
piece.imgTile.handle_y = -yDiff + h2
End Select
Case TJigsaw.BELOW_PIECE
Print "below " + targetRotation
Select targetRotation
Case 360
piece.imgTile.handle_x = yDiff + h2
piece.imgTile.handle_y = xDiff + w2
Case 90
piece.imgTile.handle_x = xDiff + w2
piece.imgTile.handle_y = yDiff + h2
Case 180
piece.imgTile.handle_x = yDiff + h2
piece.imgTile.handle_y = -xDiff + w2
Case 270
piece.imgTile.handle_x = xDiff + w2
piece.imgTile.handle_y = -yDiff + h2
End Select
Case TJigsaw.LEFT_PIECE
Print "left " + targetRotation

Select targetRotation
Case 360
piece.imgTile.handle_x = -yDiff + h2
piece.imgTile.handle_y = xDiff + w2
Case 90
piece.imgTile.handle_x = xDiff + w2
piece.imgTile.handle_y = yDiff + h2
Case 180
piece.imgTile.handle_x = yDiff + h2
piece.imgTile.handle_y = xDiff + w2

Case 270
piece.imgTile.handle_x = -xDiff + w2
piece.imgTile.handle_y = yDiff + h2
End Select
Case TJigsaw.RIGHT_PIECE
Print "right " + targetRotation

Select targetRotation
Case 360
piece.imgTile.handle_x = -yDiff + h2
piece.imgTile.handle_y = xDiff + w2

Case 90
piece.imgTile.handle_x = xDiff + w2
piece.imgTile.handle_y = yDiff + h2
Case 180
piece.imgTile.handle_x = yDiff + h2
piece.imgTile.handle_y = xDiff + w2
Case 270
piece.imgTile.handle_x = -xDiff + w2
piece.imgTile.handle_y = yDiff + h2
End Select

End Select

Print "newhandle: " + piece.imgTile.handle_x + " , " + piece.imgTile.handle_y

' set the piece to the startpiece location to rotate around it using the handle offset
piece.x = startPiece.x
piece.y = startPiece.y

piece.startRotation = piece.rotation
piece.targetRotation = targetRotation
piece.rotationTweenTimer = 0
piece.startRotationTween = True

doneMap.Insert(nextPiece.id, nextPiece)
SetUpRotationConnected(startPiece, piece, doneMap)
EndIf
EndIf
Next
End Method

Method SetUpRotation()
Print ">>>>>> setting rotation for " + Self.id

Self.startRotation = Self.rotation
Self.targetRotation = Self.startRotation + 90
Self.rotationTweenTimer = 0
Self.startRotationTween = True

Self.tx = Self.x
Self.ty = Self.y
Print x + "," + y
Local doneMap:TMap = New TMap
SetUpRotationConnected(Self, Self, doneMap)
End Method

Method StopRotation()
Self.rotation = 0
Self.startRotation = 0
Self.targetRotation = 0
Self.rotationTweenTimer = 0
Self.startRotationTween = False
End Method
 
Method MoveTween()
If startMoveTween

If moveTweenTimer < 1
moveTweenTimer:+moveTweenSpeedTimer
Self.x = TweenSmooth(Self.ox, Self.tx, Self.moveTweenTimer)
Self.y = TweenSmooth(Self.oy, Self.ty, Self.moveTweenTimer)
Else
moveTweenTimer = 1
Self.x = Self.tx
Self.y = Self.ty
startMoveTween = False

End If
End If
End Method

Method RotateTween()
If startRotationTween
If rotationTweenTimer < 1
checkPosition = False
rotationTweenTimer:+rotationTweenSpeedTimer
Self.rotation = TweenSmooth(Self.startRotation, Self.targetRotation, Self.rotationTweenTimer)
Else
rotationTweenTimer = 1
Self.rotation = Self.targetRotation
startRotationTween = False
If Self.rotation Mod 360 = 0 Then
Self.rotation = 0
Self.targetRotation = 0
checkPosition = True
EndIf

Self.x = Self.tx
Self.y = Self.ty
Print id + ": after rotation: " + x + "," + y

Self.imgTile.handle_x = originalHandleX
Self.imgTile.handle_y = originalHandleY

' rejig the tiles locations
Local doneMap:TMap = New TMap
MoveConnectedPieces(Self, Self.connectedPieces, doneMap)

EndIf
EndIf
EndMethod

Method Update()
MoveTween()
RotateTween()

If shadowOffsetX >= 0
shadowOffsetX:-shadowSpeed
shadowOffsetY:-shadowSpeed
EndIf
End Method
       
Method Draw(offsetX:Int, offsetY:Int)
SetScale(scaleX, scaleY)
SetRotation(rotation)
If selected
DrawImage(imgTile, x + offsetX + selectedOffset, y + offsetY + selectedOffset)
Else
DrawImage(imgTile, x + offsetX, y + offsetY)
EndIf
SetRotation(0)

If SHOW_TYP = 1
Local t:String
For Local i:Int = EachIn sideTypes
If i = TJigsaw.SIDE_HOLE Then t:+"H"
If i = TJigsaw.SIDE_NOSE Then t:+"N"
If i = TJigsaw.SIDE_EMPTY Then t:+"F"
Next
DrawText(t, x + offsetX + 70, y + offsetY + 70)
EndIf

If SHOW_ID
DrawText("id = " + Self.id, x, y - 60)
For Local i:Int = 0 Until Self.neighbourPieces.Length
Local piece:TJigsawTile = Self.neighbourPieces[i]
If piece
Select i
Case TJigsaw.ABOVE_PIECE
DrawText("A:" + piece.id, x, y - 20)
Case TJigsaw.BELOW_PIECE
DrawText("B:" + piece.id, x, y + 20)
Case TJigsaw.RIGHT_PIECE
DrawText("R:" + piece.id, x + 30, y)
Case TJigsaw.LEFT_PIECE
DrawText("L:" + piece.id, x - 40, y)
End Select

EndIf
Next
EndIf

If SHOW_TILE_ORIGIN
SetColor(255, 255, 0)
Local r:Float = 8
DrawOval(x + offsetX - r / 2, y + offsetY - r / 2, r, r)
SetColor(255, 255, 255)
EndIf

' DrawText(Self.rotation + " " + Self.targetRotation, x, y)

SetScale(1, 1)
SetAlpha(1)
End Method


Method _GenerateShape()
pixShape = tileConfig.basePixShape.Copy()

Local noseSize:Int = tileConfig.basePixNoseShape.width

Select sideTypes[0]
Case TJigsaw.SIDE_HOLE
_CutMask(pixShape, tileConfig.basePixNoseShape, TJigsaw.SIDE_TOP, tileConfig.borderSize + sideOffsets[0], tileConfig.borderSize)
Case TJigsaw.SIDE_NOSE
_AddMask(pixShape, tileConfig.basePixNoseShape, TJigsaw.SIDE_TOP, tileConfig.borderSize + sideOffsets[0], tileConfig.borderSize - 1)
End Select
Select sideTypes[1]
Case TJigsaw.SIDE_HOLE
_CutMask(pixShape, tileConfig.basePixNoseShape, TJigsaw.SIDE_RIGHT, tileConfig.borderSize + tileConfig.baseSize - 1, tileConfig.borderSize + sideOffsets[1])
Case TJigsaw.SIDE_NOSE
_AddMask(pixShape, tileConfig.basePixNoseShape, TJigsaw.SIDE_RIGHT, tileConfig.basesize + tileConfig.borderSize, tileConfig.borderSize + sideOffsets[1])
End Select
Select sideTypes[2]
Case TJigsaw.SIDE_HOLE
_CutMask(pixShape, tileConfig.basePixNoseShape, TJigsaw.SIDE_BOTTOM, tileConfig.borderSize + sideOffsets[2], tileConfig.baseSize + tileConfig.borderSize - 1)
Case TJigsaw.SIDE_NOSE
_AddMask(pixShape, tileConfig.basePixNoseShape, TJigsaw.SIDE_BOTTOM, tileConfig.borderSize + sideOffsets[2], tileConfig.baseSize + tileConfig.borderSize)
End Select
Select sideTypes[3]
Case TJigsaw.SIDE_HOLE
_CutMask(pixShape, tileConfig.basePixNoseShape, TJigsaw.SIDE_LEFT, tileConfig.borderSize, tileConfig.borderSize + sideOffsets[3])
Case TJigsaw.SIDE_NOSE
_AddMask(pixShape, tileConfig.basePixNoseShape, TJigsaw.SIDE_LEFT, tileConfig.borderSize - 1, tileConfig.borderSize + sideOffsets[3])
End Select
End Method


Method _GenerateShadowImage()
'shadow must be a bit bigger
Local shadowPix:TPixmap = CreatePixmap(pixShape.width + 10, pixShape.height + 10, pixShape.format)
shadowPix.ClearPixels(0)
shadowPix.Paste(pixShape, 5, 5)
blurPixmap(shadowPix, shadowBlurStrength, $ffffff00)

imgShadow = LoadImage(shadowPix, FILTEREDIMAGE)
End Method


Method _GenerateOutlineImage()
'outline must be a bit bigger
imgOutline = ConvertToOutLine(pixShape, 3, 0.75, $ffffffff, 4)
Local outlinePix:TPixmap = LockImage(imgOutline)
blurPixmap(outlinePix, 0.7, $FF000000)

imgOutline = LoadImage(outlinePix, FILTEREDIMAGE)
End Method


Method _GenerateGridBackgroundImage()
'outline must be a bit bigger
imgGridBackground = ConvertToOutLine(pixShape, 2, 0.8, $66ffffff, 4)
Local outlinePix:TPixmap = LockImage(imgGridBackground)
blurPixmap(outlinePix, 0.7, $00000000)

imgGridBackground = LoadImage(outlinePix, FILTEREDIMAGE)
End Method


Method _GenerateTileImage(jigsawTexture:TPixmap)
pixTile = CreatePixmap(pixShape.width, pixShape.height, PF_RGBA8888)
pixTile.ClearPixels(0)

If ONLY_MASK = 0
For Local pixX:Int = 0 Until pixTile.width
For Local pixY:Int = 0 Until pixTile.height
Local maskAlpha:Int = (pixShape.ReadPixel(pixX, pixY) Shr 24) & $ff
'skip invisible
If maskAlpha <= 0 Then Continue

Local sourceColor:Int
If ONLY_BLUE = 0
sourceColor = jigsawTexture.ReadPixel(pixX + (column - 1) * tileConfig.baseSize - tileConfig.borderSize, pixY + (row - 1) * tileConfig.baseSize - tileConfig.borderSize)
Else
sourceColor = $ff7777ff
EndIf
'remove source alpha and add mask alpha
pixTile.WritePixel(pixX, pixY, (sourceColor & $00FFFFFF) + (maskAlpha * $1000000))
Next
Next

_AddLightEffect(pixTile, pixShape)
EndIf

imgTile = LoadImage(pixTile, FILTEREDIMAGE)
End Method


Function _AddLightEffect(pix:TPixmap, mask:TPixmap, lightDirection:Int = 0)
If LIGHT_OFF = 1 Return

Local now:Int = 0
Local c:Int

'scan along pixel lines and light up first opacque pixel and
'darken the ones before the first transparent one, rinse repeat
For Local pixX:Int = 0 Until pix.width
now = 0
For Local pixY:Int = 0 Until pix.height
c = mask.ReadPixel(pixX, pixY)
If now = 0 And c <> 0
pix.WritePixel(pixX, pixY, RiseColor(1.3, pix.ReadPixel(pixX, pixY)))
If pixY + 1 < pix.height
pix.WritePixel(pixX, pixY + 1, RiseColor(1.4, pix.ReadPixel(pixX, pixY + 1)))
EndIf
now = 1
ElseIf now = 1 And c = 0
If pixY - 2 >= 0
pix.WritePixel(pixX, pixY - 2, RiseColor(0.7, pix.ReadPixel(pixX, pixY - 2)))
EndIf
If pixY - 1 >= 0
pix.WritePixel(pixX, pixY - 1, RiseColor(0.8, pix.ReadPixel(pixX, pixY - 1)))
EndIf
now = 0
EndIf
Next
Next
For Local pixY:Int = 0 Until pix.height
now = 0
For Local pixX:Int = 0 Until pix.width
c = mask.ReadPixel(pixX, pixY)
If now = 0 And c <> 0
pix.WritePixel(pixX, pixY, RiseColor(1.4, pix.ReadPixel(pixX, pixY)))
If pixX + 1 < pix.width
pix.WritePixel(pixX + 1, pixY, RiseColor(1.4, pix.ReadPixel(pixX + 1, pixY)))
EndIf
now = 1
ElseIf now = 1 And c = 0
If pixX - 2 >= 0
pix.WritePixel(pixX - 2, pixY, RiseColor(0.7, pix.ReadPixel(pixX - 2, pixY)))
EndIf
If pixX - 1 >= 0
pix.WritePixel(pixX - 1, pixY, RiseColor(0.7, pix.ReadPixel(pixX - 1, pixY)))
EndIf
now = 0
EndIf
Next
Next


'yep, function in a function!
Function RiseColor:Int(f:Float, color:Int)
Local r:Int = (color & $00ff0000) / $10000
Local g:Int = (color & $0000ff00) / $100
Local b:Int = (color & $000000ff)
r = Int(Min(r * f, 255)) * $10000
g = Int(Min(g * f, 255)) * $100
b = Int(Min(b * f, 255))
Return (color & $ff000000) | r | g | b
End Function
End Function


Function _AddMask(base:TPixmap, mask:TPixmap, side:Int, offsetX:Int, offsetY:Int)
If base.width < mask.width + offsetX Then Throw "TJigsawTile - mask too big (width)"
If base.height < mask.height + offsetY Then Throw "TJigsawTile - mask too big (height)"

For Local maskX:Int = 0 Until mask.width
For Local maskY:Int = 0 Until mask.height
Local maskColor:Int = mask.ReadPixel(maskX, maskY)
If maskColor = 0 Then Continue

Select side
Case TJigsaw.SIDE_RIGHT
base.WritePixel(offsetX + maskX, offsetY + maskY, maskColor)
Case TJigsaw.SIDE_LEFT
base.WritePixel(offsetX - maskX, offsetY + maskY, maskColor)
Case TJigsaw.SIDE_BOTTOM
base.WritePixel(offsetX + maskY, offsetY + maskX, maskColor)
Case TJigsaw.SIDE_TOP
base.WritePixel(offsetX + maskY, offsetY - maskX, maskColor)
End Select
Next
Next
End Function


Function _CutMask(base:TPixmap, mask:TPixmap, side:Int, offsetX:Int, offsetY:Int)
If base.width < mask.width + offsetX Then Throw "TJigsawTile - mask too big (width)"
If base.height < mask.height + offsetY Then Throw "TJigsawTile - mask too big (height)"

For Local maskX:Int = 0 Until mask.width
For Local maskY:Int = 0 Until mask.height
'for cuts/holes we reduce alpha of the pixmap's pixel
'by what is left (so NOSE alpha 255 cuts 100%, 0 cuts 0%)
Local maskColor:Int = mask.ReadPixel(maskX, maskY)
If maskColor = 0 Then Continue

Local maskAlpha:Int = (maskColor Shr 24) & $ff

Select side
Case TJigsaw.SIDE_RIGHT
'remove original alpha and add back "alpha mix"
base.WritePixel(offsetX - maskX, offsetY + maskY, (base.ReadPixel(offsetX - maskX, offsetY + maskY) & $00FFFFFF) + (255 - maskAlpha) * $1000000)
Case TJigsaw.SIDE_LEFT
'remove original alpha and add back "alpha mix"
base.WritePixel(offsetX + maskX, offsetY + maskY, (base.ReadPixel(offsetX + maskX, offsetY + maskY) & $00FFFFFF) + (255 - maskAlpha) * $1000000)
Case TJigsaw.SIDE_BOTTOM
'remove original alpha and add back "alpha mix"
base.WritePixel(offsetX + maskY, offsetY - maskX, (base.ReadPixel(offsetX + maskY, offsetY - maskX) & $00FFFFFF) + (255 - maskAlpha) * $1000000)
Case TJigsaw.SIDE_TOP
'remove original alpha and add back "alpha mix"
base.WritePixel(offsetX + maskY, offsetY + maskX, (base.ReadPixel(offsetX + maskY, offsetY + maskX) & $00FFFFFF) + (255 - maskAlpha) * $1000000)
End Select
Next
Next
End Function
End Type


 
 
Type TJigsaw
'tiles are ordered by their creation order - or manual drag/drop
Field tiles:TList = New TList
Field tilesReversed:TList = New TList
Field Columns:Int, Rows:Int
'position of jigsaw on screen
Field x:Int, y:Int
Field w:Int = -1, h:Int = -1
'offset of image from jigsaw origin
Field imageOffsetX:Int = 100, imageOffsetY:Int = 100

Field selectedTile:TJigsawTile
Field hoveredTile:TJigsawTile

Field tileConfig:TJigsawTileConfig
Field jigsawPicture:TPixmap

Const SIDE_EMPTY:Int = 0
Const SIDE_NOSE:Int = 1
Const SIDE_HOLE:Int = 2
Const SIDE_LEFT:Int = 4
Const SIDE_TOP:Int = 8
Const SIDE_RIGHT:Int = 16
Const SIDE_BOTTOM:Int = 32
Const ABOVE_PIECE:Int = 0, BELOW_PIECE:Int = 1, LEFT_PIECE:Int = 2, RIGHT_PIECE:Int = 3



Method Init()
tileConfig = New TJigsawTileConfig
tileConfig.basePixShape = LoadPixmap("jigsawMiddleMask.png")
tileConfig.basePixNoseShape = LoadPixmap("jigsawTabMask.png")

jigsawPicture = LoadPixmap("jigsawPhoto.png")

If Not tileConfig.basePixShape Then Throw "jigsawMiddleMask.png not found"
If Not tileConfig.basePixNoseShape Then Throw "jigsawTabMask.png not found"
If Not jigsawPicture Then Throw "jigsawPhoto.png not found"


CalculateSizes()

GenerateTiles()
If SORTED = 0
ShuffleTiles()
Else
For Local tile:TJigsawTile = EachIn tiles
tile.rotation = 0
tile.x = (tile.column - 1) * (tileConfig.size - 20) + 100
tile.y = (tile.row - 1) * (tileConfig.size - 20) + 100
Next
EndIf
End Method
 
 
Method CalculateSizes()
Print "GenerateTiles()"
tileConfig.size = Min(tileConfig.basePixShape.width, tileConfig.basePixShape.height)
tileConfig.baseSize = _FindTileBaseWidth(tileConfig.basePixShape)
tileConfig.borderSize = (tileConfig.size - tileConfig.baseSize) / 2
Print "  size=" + tileConfig.size + " (base=" + tileConfig.baseSize + " borders=" + tileConfig.borderSize + ")"

'if image is bigger than puzzle size (eg some pixels too wide)
'we simply ignore the "rest"
'but if too small to fit at least once ... we fail
columns = jigsawPicture.width / tileConfig.baseSize
rows = jigsawPicture.height / tileConfig.baseSize
Print "  columns=" + columns + " rows=" + rows

If columns = 0 Or rows = 0
RuntimeError "ERROR: picture too small (" + jigsawPicture.width + "x" + jigsawPicture.height + ", minimum required: " + tileConfig.baseSize + "x" + tileConfig.baseSize + ")"
EndIf
End Method

 
'scans y-center from left to right to find first opacque point
'assume inner part is "centered" (left space = right space)
Function _FindTileBaseWidth:Int(pix:TPixmap)
For Local x:Int = 0 Until pix.width
If pix.ReadPixel(x, pix.height / 2) <> 0
Return pix.width - 2 * x
EndIf
Next
RuntimeError "_FindTileBaseWidth: Failed to find non-transparent point on vertical center line"
End Function


Method GenerateTiles()
Print "GenerateTiles()"

Local startt:Int = MilliSecs()

SeedRnd MilliSecs()
Local id:Int = 0
For Local row:Int = 1 To rows
For Local col:Int = 1 To columns
Local sideTypes:Int[] = New Int[4]
Local sideOffsets:Int[] = New Int[4]

'top
'first row?
If row = 1
sideTypes[0] = SIDE_EMPTY
Else
Local neighbourTop:TJigsawTile = GetTile(col, row - 1)
If neighbourTop
If neighbourTop.sideTypes[2] = SIDE_HOLE
sideTypes[0] = SIDE_NOSE
Else
sideTypes[0] = SIDE_HOLE
EndIf
sideOffsets[0] = neighbourTop.sideOffsets[2]
EndIf
EndIf


'right
'last column?
If col = columns
sideTypes[1] = SIDE_EMPTY
Else
Select Rand(0, 1)
Case 0
sideTypes[1] = SIDE_HOLE
Case 1
sideTypes[1] = SIDE_NOSE
End Select
'TODO: anders definieren
sideOffsets[1] = Rand(30, 65)
EndIf

'bottom
'last row?
If row = rows
sideTypes[2] = SIDE_EMPTY
Else
Select Rand(0, 1)
Case 0
sideTypes[2] = SIDE_HOLE
Case 1
sideTypes[2] = SIDE_NOSE
End Select
'TODO: anders definieren
sideOffsets[2] = Rand(25, 70)
EndIf

'left
'first column?
If col = 1
sideTypes[3] = SIDE_EMPTY
Else
Local neighbourLeft:TJigsawTile = GetTile(col - 1, row)
If neighbourLeft
If neighbourLeft.sideTypes[1] = SIDE_HOLE
sideTypes[3] = SIDE_NOSE
Else
sideTypes[3] = SIDE_HOLE
EndIf
sideOffsets[3] = neighbourLeft.sideOffsets[1]
EndIf
EndIf


Local tile:TJigsawTile = New TJigsawTile.Init(id, jigsawPicture, tileConfig, sideTypes, sideOffsets, col, row)

tiles.AddFirst(tile)
tilesReversed.AddLast(tile)
id:+1
Next
Next
SetNeighbours()

Local endt:Int = MilliSecs()

Print "Time taken to generate tiles etc: " + (endt - startt) + " ms"
End Method


Method SetNeighbours()
For Local p:TJigsawTile = EachIn tiles
Local x:Int = p.row
Local y:Int = p.column

For Local piece:TJigsawTile = EachIn tiles
If piece <> p
For Local i:Int = -1 To 1
For Local j:Int = -1 To 1
If i = 0 Or j = 0
If piece.row = x + i And piece.column = y + j
If i = 0
If j = -1
p.neighbourPieces[LEFT_PIECE] = piece
Else
p.neighbourPieces[RIGHT_PIECE] = piece
EndIf
End If
If j = 0
If i = -1
p.neighbourPieces[ABOVE_PIECE] = piece
Else
p.neighbourPieces[BELOW_PIECE] = piece
End If
End If
EndIf
EndIf
Next
Next
EndIf
Next
Next
End Method

Method ShuffleTiles:Int()
For Local tile:TJigsawTile = EachIn tiles
Local r:Int = Rand(0, 3)
Local rotation:Int
Select r
Case 0
rotation = 0
Case 1
rotation = 90
Case 2
rotation = 180
Case 3
rotation = 270
End Select
tile.rotation = rotation
tile.targetRotation = rotation
tile.x = Rand(GraphicsWidth() - 300) + 200
tile.y = Rand(GraphicsHeight() - 300) + 200
Next
End Method


Method GetTile:TJigsawTile(column:Int, row:Int)
For Local t:TJigsawTile = EachIn tiles
If t.column = column And t.row = row
Return t
EndIf
Next
Return Null
End Method


Method IsTileOverTargetCell:Int(tile:TJigsawTile)
'print "IsTileOverTargetCell: " + " x="+tile.x +"  y="+tile.y
'print "IsTileOverTargetCell: " + " currCol="+((tile.x - imageOffsetX + tileConfig.baseSize/2) / tileConfig.baseSize + 1)+" tarCol="+tile.column
'print "IsTileOverTargetCell: " + " currRow="+((tile.y - imageOffsetY + tileConfig.baseSize/2) / tileConfig.baseSize + 1)+" tarRow="+tile.row
Return ((tile.x - imageOffsetX + tileConfig.baseSize / 2) / tileConfig.baseSize) + 1 = tile.column And ((tile.y - imageOffsetY + tileConfig.baseSize / 2) / tileConfig.baseSize) + 1 = tile.row
End Method


Method PlaceTileOnTargetCell(tile:TJigsawTile)
tile.StopRotation()
tile.x = (tile.column - 1) * tileConfig.baseSize + imageOffsetX
tile.y = (tile.row - 1) * tileConfig.baseSize + imageOffsetY
tile.placed = True
End Method


Method TryToPlaceTile:Int(tile:TJigsawTile)
If Not tile Then Return False

CheckTiles(tile)

rem
If IsTileOverTargetCell(tile)
If tile.rotation = 0

PlaceTileOnTargetCell(tile)
DropTile(tile)

tiles.Remove(tile)
tiles.AddFirst(tile)
tilesReversed = tiles.Reversed()

Return True
EndIf
EndIf
endrem
Return False
End Method


Method Update(camera:TCamera)
If Not selectedTile
hoveredTile = jigsaw.GetTileAtXY(camera.MouseX() - x, camera.MouseY() - y)
EndIf
For Local t:TJigsawTile = EachIn tiles
t.Update()
If t.checkPosition
TryToPlaceTile(t)

End If
Next
End Method

Method CheckTiles(tile:TJigsawTile)
Local doneMap:TMap = New TMap
CheckBoardR(tile, doneMap)
End Method

Method CheckBoardR(startPiece:TJigsawTile, doneMap:TMap)

For Local i:Int = 0 Until startPiece.neighbourPieces.Length
Local piece:TJigsawTile = startPiece.neighbourPieces[i]
Local addPiece:Int = False
If piece

If Not doneMap.Contains(piece.id + startPiece.id)
If piece.rotation = startPiece.rotation

If Distance(piece.x, piece.y, startPiece.x, startPiece.y) < tileConfig.baseSize + (tileConfig.borderSize / 2)
Local horDist:Int = Distance(piece.x, 0, startPiece.x, 0)
Local vertDist:Int = Distance(0, piece.y, 0, startPiece.y)
' Print i + " " + ABOVE_PIECE
Select i
Case ABOVE_PIECE
piece.connectedPieces[BELOW_PIECE] = startPiece
startPiece.connectedPieces[ABOVE_PIECE] = piece
Case BELOW_PIECE
piece.connectedPieces[ABOVE_PIECE] = startPiece
startPiece.connectedPieces[BELOW_PIECE] = piece
Case LEFT_PIECE
piece.connectedPieces[LEFT_PIECE] = startPiece
startPiece.connectedPieces[RIGHT_PIECE] = piece
Case RIGHT_PIECE
piece.connectedPieces[RIGHT_PIECE] = startPiece
startPiece.connectedPieces[LEFT_PIECE] = piece
End Select
EndIf
End If
If addPiece
' AllConnectedPieces.Insert(piece.id, piece)
EndIf
doneMap.Insert(piece.id + startPiece.id, piece)
CheckBoardR(piece, doneMap)
EndIf
EndIf
Next

EndMethod

Method Draw()

SetColor 152, 122, 35
DrawRect(imageOffsetX - tileConfig.baseSize / 2 - 3, imageOffsetY - tileConfig.baseSize / 2 - 3, tileConfig.baseSize * columns + 6, tileConfig.baseSize * rows + 6)
SetColor 222, 155, 55
DrawRect(imageOffsetX - tileConfig.baseSize / 2 + 3, imageOffsetY - tileConfig.baseSize / 2 + 3, tileConfig.baseSize * columns - 6, tileConfig.baseSize * rows - 6)
SetColor 255, 255, 255

'puzzle grid
If SHOW_GRID = 1
For Local r:Int = 0 To rows ' from 0 so it contains "begin" too
Local yy:Int = imageOffsetY + r * tileConfig.baseSize - tileConfig.baseSize / 2
DrawLine(imageOffsetX, yy, imageOffsetX + columns * tileConfig.baseSize, yy)
Next
For Local c:Int = 0 To columns
Local xx:Int = imageOffsetX + c * tileConfig.baseSize - tileConfig.baseSize / 2
DrawLine(xx, imageOffsetY, xx, imageOffsetY + rows * tileConfig.baseSize)
Next
ElseIf SHOW_GRID = 2
For Local t:TJigsawTile = EachIn tiles
If t.imgGridBackground
DrawImage(t.imgGridBackground, imageOffsetX + (t.column - 1) * tileConfig.baseSize - tileConfig.baseSize / 2 - tileConfig.borderSize - 5, imageOffsetY + (t.row - 1) * tileConfig.baseSize - tileConfig.baseSize / 2 - tileConfig.borderSize - 5)
EndIf
Next
EndIf

DrawTiles()
End Method


Method DrawTiles()
'draw layer of base tiles (so exclude top most one)
For Local t:TJigsawTile = EachIn tiles
If t <> selectedTile Then t.DrawShadow(x, y)
Next

'draw tile itself
For Local t:TJigsawTile = EachIn tiles
If t = selectedTile Then t.DrawShadow(x, y)

t.Draw(x, y)
If hoveredTile = t
SetBlend LIGHTBLEND
SetAlpha Float(0.1 + 0.1 * Sin(MilliSecs() * 0.2))

t.Draw(x, y)

SetBlend ALPHABLEND
SetAlpha 1.0
EndIf

If hoveredTile = t
SetAlpha 0.9
t.DrawOutline(x, y)
SetAlpha 1.0
EndIf

'If t = hoveredTile Then t.DrawOutline(x, y)
Next

'draw hover on top of all
If hoveredTile
SetAlpha 0.4
hoveredTile.DrawOutline(x, y)
SetAlpha 1.0
EndIf
End Method


Method HoverTile:Int(t:TJigsawTile)
If t <> hoveredTile
hoveredTile = t
Return True
EndIf
Return False
End Method


Method SelectTile:Int(t:TJigsawTile)
If selectedTile Then selectedTile.selected = False

If t <> selectedTile
If t Then t.selected = True

selectedTile = t

Return True
EndIf
Return False
End Method


Method DropTile(t:TJigsawTile)
If t
If t = selectedTile Then selectedTile = Null
t.selected = False
EndIf
End Method


Method DragTile(t:TJigsawTile)
'move it on top
tiles.Remove(t)
tiles.AddLast(t)
tilesReversed = tiles.Reversed()
End Method

       
Method GetTileAtXY:TJigsawTile(x:Int, y:Int, lookupRange:Int = -1)
If lookupRange = -1 Then lookupRange = tileConfig.baseSize / 2

For Local t:TJigsawTile = EachIn tilesReversed
If t.placed Then Continue

If Abs(x - t.x) < lookupRange
If Abs(y - t.y) < lookupRange
Return t
EndIf
EndIf
Next
Return Null
End Method
End Type

Type TCamera
Field x:Float, y:Float
Field targetX:Float, targetY:Float
Field zoom:Float = 1
Field targetZoom:Float = zoom
Field zoomSpeed:Float = 0.05
Field scrollSpeed:Float = 0.05
Field originalWidth:Float, originalHeight:Float
Field width:Float, height:Float
Field minZoom:Float = 0.1, maxZoom:Float = 3
Field mX:Float, mY:Float
Field minLimitX:Int, maxLimitX:Int
Field minLimitY:Int, maxLimitY:Int
Field displayInfo:Int = False

Function Create:TCamera(width:Float, height:Float)
Local c:TCamera = New TCamera
c.originalWidth = width
c.originalHeight = height
c.minLimitX = 0
c.maxLimitX = width
c.minLimitY = 0
c.maxLimitY = height
Return c
End Function

Method SetDisplayInfo(b:Int)
displayInfo = b
End Method

Method DrawInfo(dx:Int, dy:Int, gap:Int = 20)
Local displayY:Int = dy
SetAlpha(0.5)
SetColor(0, 0, 0)
DrawRect(dx, dy, 400, 9 * gap)
SetAlpha(1)
SetColor(255, 255, 255)
DrawText("Camera Info:", dx, displayY) ; displayY:+gap
DrawText("x,y = " + x + "," + y, dx, displayY) ; displayY:+gap
DrawText("mouse x,y = " + Self.MouseX() + "," + Self.MouseY(), dx, displayY) ; displayY:+gap
DrawText("zoom = " + zoom, dx, displayY) ; displayY:+gap
DrawText("target zoom = " + targetZoom, dx, displayY) ; displayY:+gap
DrawText("w,h = " + width + ", " + height, dx, displayY) ;displayY:+gap
DrawText("target x,y = " + targetX + "," + targetY, dx, displayY) ; displayY:+gap
DrawText("limitX min,max = " + minLimitX + "," + maxLimitX, dx, displayY) ; displayY:+gap
DrawText("limitY min,max = " + minLimitY + "," + maxLimitY, dx, displayY) ; displayY:+gap
End Method

Method MouseX:Float()
Return Self.mX
End Method

Method MouseY:Float()
Return Self.mY
End Method

Method SetXY(tx:Float, ty:Float)
Self.x = -tx
Self.y = -ty
LimitScroll()
EndMethod

Method SetScrollSpeed(speed:Float)
Self.scrollSpeed = speed
End Method

Method SetZoomSpeed(speed:Float)
Self.zoomSpeed = speed
End Method

Method SetMinZoom(m:Float)
Self.minZoom = m
End Method

Method SetMaxZoom(m:Float)
Self.maxZoom = m
End Method

Method SetLimits(minX:Int, minY:Int, maxX:Int, maxY:Int)
Self.minLimitX = minX
Self.minLimitY = minY
Self.maxLimitX = maxX
Self.maxLimitY = maxY
LimitScroll()
End Method

Method LimitScroll()
If Self.x > - Self.minLimitX Then Self.x = -Self.minLimitX
If Self.x < - Self.maxLimitX Then Self.x = -Self.maxLimitX
If Self.y > - Self.minLimitY Then Self.y = -Self.minLimitY
If Self.y < - Self.maxLimitY Then Self.y = -Self.maxLimitY
End Method

Method Start:Int(tx:Float, ty:Float, scrollToTarget:Int = True)
If Self.targetZoom < Self.minZoom Then Self.targetZoom = Self.minZoom
If Self.targetZoom > Self.maxZoom Then Self.targetZoom = Self.maxZoom
Self.targetX = tx
Self.targetY = ty
If scrollToTarget
Self.x:-(Self.targetX + Self.x) * Self.scrollSpeed
Self.y:-(Self.targetY + Self.y) * Self.scrollSpeed
Else
Self.x = -tx
Self.y = -ty
EndIf

LimitScroll()

Self.zoom:+(self.targetZoom - Self.zoom) * self.zoomSpeed
SetVirtualResolution(Self.originalWidth * Self.zoom, Self.originalHeight * Self.zoom)
Self.width = VirtualResolutionWidth()
Self.height = VirtualResolutionHeight()

Local ox:Float = Self.x + Self.width *.5
Local oy:Float = Self.y + Self.height *.5
SetOrigin(ox, oy)

Self.mX = VirtualMouseX() - ox
Self.mY = VirtualMouseY() - oy

If displayInfo
SetColor(255, 0, 0)
Local minX:Float = Self.minLimitX
Local maxX:Float = Self.maxLimitX
Local minY:Float = Self.minLimitY
Local maxY:Float = Self.maxLimitY

DrawLine(minX, minY, maxX, minY)
DrawLine(minX, minY, minX, maxY)
DrawLine(maxX, minY, maxX, maxY)
DrawLine(minX, maxY, maxX, maxY)
SetColor(255, 255, 255)
EndIf
End Method

Method Stop:Int()
SetVirtualResolution(Self.originalWidth, Self.originalHeight)
SetOrigin(0, 0)
if displayInfo
DrawInfo(10, 10)
EndIf
End Method
EndType

'extracted from https://github.com/GWRon/Dig/base.gfx.imagehelper.bmx
 
Function ConvertToOutLine:TImage(imageOrPixmap:Object, lineThickness:Int=1, alphaTreshold:Float = 0.0, outlineColor:Int=-1, targetPadding:Int=0, imageFlags:Int = -1)
If outlineColor = -1 Then outlineColor = -1 '(Int(255 * $1000000) + Int(255 * $10000) + Int(255 * $100) + Int(255))
lineThickness = lineThickness / 2
 
'convert 0.0-1.0 to 0-255
alphaTreshold :* 255
 
If imageFlags = -1 And TImage(imageOrPixmap)
imageFlags = TImage(imageOrPixmap).flags
Else
imageFlags = 0
EndIf
 
'load source
Local srcPix:TPixmap
If TPixmap(imageOrPixmap)
srcPix = TPixmap(imageOrPixmap)
Else
srcPix = LockImage(TImage(imageOrPixmap))
EndIf
If Not srcPix Then Return Null
'if it is the wrong format to use, create a temporary copy
If srcPix.format <> PF_RGBA8888
srcPix = srcPix.Copy().Convert(PF_RGBA8888)
EndIf
 
'create target
Local targetPix:TPixmap = CreatePixmap(srcPix.width + targetPadding*2, srcPix.height + targetPadding*2, srcPix.format)
targetPix.ClearPixels(0)
 
'storage for alpha of a pixel's surrounding pixels
Local sum:Int
 
For Local x:Int = 0 Until srcPix.width
For Local y:Int = 0 Until srcPix.height
If (x <> 0 And x <> srcPix.width-1) And (y <> 0 And y <> srcPix.height-1)
If ((ReadPixel(srcPix, x, y) Shr 24) & $ff) > alphaTreshold
Continue
EndIf
EndIf
sum = 0
If x > 0               Then sum :+ ((ReadPixel(srcPix, x-1, y) Shr 24) & $ff) > alphaTreshold
If x < srcPix.width-1  Then sum :+ ((ReadPixel(srcPix, x+1, y) Shr 24) & $ff) > alphaTreshold
If y > 0               Then sum :+ ((ReadPixel(srcPix, x, y-1) Shr 24) & $ff) > alphaTreshold
If y < srcPix.height-1 Then sum :+ ((ReadPixel(srcPix, x, y+1) Shr 24) & $ff) > alphaTreshold
   
If sum > 0
If lineThickness = 0
WritePixel(targetPix, x + targetPadding, y + targetPadding, outlineColor )
Else
For Local i:Int = 0 To lineThickness
If x + targetPadding - i >= 0               Then WritePixel(targetPix, x + targetPadding - i , y + targetPadding, outlineColor )
If x + targetPadding + i < targetPix.width  Then WritePixel(targetPix, x + targetPadding + i , y + targetPadding, outlineColor )
If y + targetPadding - i >= 0               Then WritePixel(targetPix, x + targetPadding, y + targetPadding - i, outlineColor )
If y + targetPadding + i < targetPix.height Then WritePixel(targetPix, x + targetPadding, y + targetPadding + i, outlineColor )
Next
EndIf
EndIf
Next
Next
 
Return LoadImage(targetPix, imageFlags)
End Function
 
 
 
 
 
Function blurPixmap:TPixmap(pm:TPixmap, k:Float = 0.5, backgroundColor:Int=0)
'pm - the pixmap to blur. Format must be PF_RGBA8888
'k - blurring amount. Value between 0.0 and 1.0
'        0.1 = Extreme, 0.9 = Minimal
 
For Local x:Int = 1 Until pm.Width
For Local z:Int = 0 Until pm.Height
WritePixel(pm, x, z, blurPixel(ReadPixel(pm, x, z), ReadPixel(pm, x - 1, z), k, backgroundColor))
Next
Next
 
For Local x:Int = (pm.Width - 3) To 0 Step -1
For Local z:Int = 0 Until pm.Height
WritePixel(pm, x, z, blurPixel(ReadPixel(pm, x, z), ReadPixel(pm, x + 1, z), k, backgroundColor))
Next
Next
 
For Local x:Int = 0 Until pm.Width
For Local z:Int = 1 Until pm.Height
WritePixel(pm, x, z, blurPixel(ReadPixel(pm, x, z), ReadPixel(pm, x, z - 1), k, backgroundColor))
Next
Next
 
For Local x:Int = 0 Until pm.Width
For Local z:Int = (pm.Height - 3) To 0 Step -1
WritePixel(pm, x, z, blurPixel(ReadPixel(pm, x, z), ReadPixel(pm, x, z + 1), k, backgroundColor))
Next
Next
 
 
'function in a function - it is just a helper
Function blurPixel:Int(px:Int, px2:Int, k:Float, backgroundColor:Int = 0)
'if there is no actual "pixel color" (eg a fully transparent black)
'then mix colors accordingly
If px2 = backgroundColor
Return Int( ..
Int(Byte(px2 Shr 24)) Shl 24 | ..
Int(Byte(px Shr 16)) Shl 16 | ..
Int(Byte(px Shr  8)) Shl  8 | ..
Int(Byte(px)) ..
  )
ElseIf px = backgroundColor
Return Int( ..
Int(Byte(px Shr 24)) Shl 24 | ..
Int(Byte(px2 Shr 16)) Shl 16 | ..
Int(Byte(px2 Shr  8)) Shl  8 | ..
Int(Byte(px2)) ..
  )
Else
Return Int( ..
Int((Byte(px2 Shr 24) * (1 - k)) + (Byte(px Shr 24) * k)) Shl 24 | ..
Int((Byte(px2 Shr 16) * (1 - k)) + (Byte(px Shr 16) * k)) Shl 16 | ..
Int((Byte(px2 Shr  8) * (1 - k)) + (Byte(px Shr  8) * k)) Shl  8 | ..
Int((Byte(px2)        * (1 - k)) + (Byte(px)        * k)) ..
  )
EndIf
End Function

Return pm
End Function

Offline therevills

  • Hero Member
  • *****
  • Posts: 729
Re: Jigsaw Pieces
« Reply #78 on: January 07, 2022, 04:26:05 »
Progress report :)

* Smooth rotation is working (thanks Ron)
* Multi-selection
* View preview full image
* Simple GUI

TODO:
* Different size pieces support
* Different size main images support
* Better piece connection collision
* Add shadows back
* Save/Load
* Other?


Online Derron

  • Hero Member
  • *****
  • Posts: 3797
Re: Jigsaw Pieces
« Reply #79 on: January 07, 2022, 09:09:46 »
- highlight currently selected ? (I had some "outline" code in my examples - just render the outlines of the grouped tiles and afterwards render all the grouped tiles itself - should do well enough)
- all your tiles have the same "peg positions" (so left side is the same etc), add variations (different begs, different offsets), next step: not just a "peg" but also a bit of the side smoothly carved off (makes the jigsaws look less regular
- hints: the option to highlight one of the "fitting" tiles for the currently selected tile
- autosolver ... select a tile, click solve and it smoothly moves into position
- autosolver2 ... or alternatively, if the tile was loose, snap another loose tile to the selected one (with "smooth visual movement")
- autosolver3 ... only omve into target position if there is at least one tile (already placed at final position) which connects with your selected tile/group
- autosolver4 ... simply move (smoothly) one random tile (not already at target) into target position -> so 1000 clicks for a 1000 tile puzzle = completed :D 


bye
Ron

Offline therevills

  • Hero Member
  • *****
  • Posts: 729
Re: Jigsaw Pieces
« Reply #80 on: January 08, 2022, 09:00:20 »
Thanks for the ideas Ron  8)

I've been stress testing it today, 1,296 pieces for a 1366 x 768 image.



Online Derron

  • Hero Member
  • *****
  • Posts: 3797
Re: Jigsaw Pieces
« Reply #81 on: January 08, 2022, 13:50:39 »
If you have two render "layers" (or render "blocks")

Code: [Select]
for tile = eachin tiles
  tile.RenderOverlay()
Next

for tile = eachin tiles
  tile.RenderTile()
Next

This way you only render the "outline" of blocks too - try it out to check what looks better, "group-outline" or "tile-outline".
(btw the same way you could render the shadows - to avoid the tiles of a group shadowing the other group tiles.


But all in all - seems to come along nicely

 

SimplePortal 2.3.6 © 2008-2014, SimplePortal