Polygon Tool: Create, Edit, Move, and Export Polygons in Solar2D

Hi everyone! I wanted to share a polygon-creation tool I’ve been developing. This tool lets you:

  1. Draw polygons by clicking/tapping points (in Create mode).
  2. Edit polygons by dragging individual vertices (in Edit mode).
  3. Move entire polygons around (in Move mode).
  4. Undo vertex placements or moves (Ctrl+Z).
  5. Delete a polygon.
  6. Export your polygons (and their coordinates) to a Lua file that can be loaded in other Solar2D projects!

The code is fairly straightforward and uses standard Solar2D APIs (display.newCircle, display.newLine, display.newPolygon) along with touch and tap events to build an interactive experience.

The code is all in one file (for simplicity).

How to Use

  1. Paste the code below in the main Lua file in a new project.

  2. Run in the simulator. You’ll see three main buttons (Create, Edit, Move) at the top, plus a Delete and Export button (optional).

  3. Create Mode:

• Click anywhere to place points.

• Once you have 3 or more, click on the first point (or press Space) to finalize.

  1. Edit Mode:

• Click on any finalized polygon to edit. Drag its vertices. Press Escape to finalize.

  1. Move Mode:

• Click on a polygon, drag it around. Press Escape to release it.

  1. Delete:

• While editing or moving, press Delete/Backspace or tap the Delete button.

  1. Export:

• Tap the Export button. This generates a Lua file in your Documents directory in the app’s sanbox (polygons_exported.lua, for example).

• In another Solar2D project, do:

local polygonsModule = require("polygons_exported")
local myPolygons = polygonsModule.createPolygons()

Voila! The same polygons appear automatically.

The full code:

-- INITIAL SETUP
--------------------------------------------------------------------------------

display.setStatusBar(display.HiddenStatusBar)

local currentMode = "create"  -- can be "create", "edit", or "move"

-- Forward references to the buttons
local createButton
local editButton
local moveButton  -- (NEW)
local deleteButtonText
local deleteButtonRect

-- POLYGON DATA STRUCTURES
--------------------------------------------------------------------------------

local polygonsData = {}

-- For creating new polygons (in CREATE mode)
local currentPolygonIndex = 1
polygonsData[currentPolygonIndex] = {
    circles        = {},
    lines          = {},
    points         = {},
    polygonCreated = false,
    finalPolygon   = nil,
    isInEditMode   = false,
    editHistory    = {}
}

-- If we are editing a polygon, we track its index here
local editModePolygonIndex = nil

-- For moving a polygon, we track its index here
local movePolygonIndex = nil

-- UTILITY: Clone a table of points (for editHistory / undo).
--------------------------------------------------------------------------------
local function clonePointsTable(points)
    local newTable = {}
    for i, v in ipairs(points) do
        newTable[i] = v
    end
    return newTable
end

-- UTILITY: Check distance for "near" detection
--------------------------------------------------------------------------------
local function isNear(x1, y1, x2, y2, radius)
    local dx = x2 - x1
    local dy = y2 - y1
    return (dx*dx + dy*dy) <= (radius*radius)
end

-- CREATE & FINALIZE POLYGON
--------------------------------------------------------------------------------
local function createPolygon(polygonIndex)
    local polyData = polygonsData[polygonIndex]
    if not polyData or polyData.polygonCreated then 
        return 
    end
    
    if #polyData.points < 6 then  -- Need at least 3 points => 6 coords
        return
    end
    
    polyData.polygonCreated = true
    
    -- Find bounding box
    local minX, minY = math.huge, math.huge
    local maxX, maxY = -math.huge, -math.huge
    for i = 1, #polyData.points, 2 do
        local x = polyData.points[i]
        local y = polyData.points[i+1]
        if x < minX then minX = x end
        if y < minY then minY = y end
        if x > maxX then maxX = x end
        if y > maxY then maxY = y end
    end
    
    local width  = maxX - minX
    local height = maxY - minY
    local offsetX = minX + width * 0.5
    local offsetY = minY + height * 0.5
    
    -- Build centered coords
    local centeredCoords = {}
    for i = 1, #polyData.points, 2 do
        local x = polyData.points[i]   - offsetX
        local y = polyData.points[i+1] - offsetY
        table.insert(centeredCoords, x)
        table.insert(centeredCoords, y)
    end
    
    -- Draw final polygon
    local polygon = display.newPolygon(offsetX, offsetY, centeredCoords)
    polygon:setFillColor(0.2, 0.7, 0.2, 0.4)
    polygon.strokeWidth = 1
    polygon:setStrokeColor(0, 0.5, 0)
    
    -- Give it a custom property to find it later
    polygon.polygonIndex = polygonIndex
    polygon:addEventListener("tap", function(event)
        return onFinalPolygonTap(event)
    end)
    
    polygonsData[polygonIndex].finalPolygon = polygon
    
    -- Remove circles/lines used during creation
    for i = #polyData.circles, 1, -1 do
        display.remove(polyData.circles[i])
        polyData.circles[i] = nil
    end
    for i = #polyData.lines, 1, -1 do
        display.remove(polyData.lines[i])
        polyData.lines[i] = nil
    end
end

local function finalizeCurrentPolygonAndStartNew()
    createPolygon(currentPolygonIndex)
    currentPolygonIndex = currentPolygonIndex + 1
    polygonsData[currentPolygonIndex] = {
        circles        = {},
        lines          = {},
        points         = {},
        polygonCreated = false,
        finalPolygon   = nil,
        isInEditMode   = false,
        editHistory    = {}
    }
end

-- EDIT MODE FUNCTIONS
--------------------------------------------------------------------------------
function finalizeEditMode(polygonIndex) end
function enterEditMode(polygonIndex) end

-- DELETE A POLYGON
--------------------------------------------------------------------------------
local function deletePolygon(polygonIndex)
    local polyData = polygonsData[polygonIndex]
    if not polyData then return end
    
    -- If it's in edit mode, remove circles/lines
    if polyData.isInEditMode then
        for i=#polyData.circles,1,-1 do
            display.remove(polyData.circles[i])
            polyData.circles[i] = nil
        end
        for i=#polyData.lines,1,-1 do
            display.remove(polyData.lines[i])
            polyData.lines[i] = nil
        end
    else
        -- Otherwise remove the final polygon
        if polyData.finalPolygon then
            display.remove(polyData.finalPolygon)
            polyData.finalPolygon = nil
        end
    end
    
    polygonsData[polygonIndex] = nil
    
    if editModePolygonIndex == polygonIndex then
        editModePolygonIndex = nil
    end
    
    -- (NEW) If we were moving this polygon, reset movePolygonIndex
    if movePolygonIndex == polygonIndex then
        movePolygonIndex = nil
    end
end

-- DRAGGING A VERTEX (CIRCLE) IN EDIT MODE
--------------------------------------------------------------------------------
function onCircleDrag(event)
    local target = event.target
    if not target then return false end
    
    local polygonIndex = target.polygonIndex
    local circleIndex  = target.circleIndex
    local polyData = polygonsData[polygonIndex]
    
    if not polyData or not polyData.isInEditMode then
        return false
    end
    
    if event.phase == "began" then
        display.currentStage:setFocus(target)
        target.isFocus = true
        target.offsetX = event.x - target.x
        target.offsetY = event.y - target.y
        
    elseif event.phase == "moved" and target.isFocus then
        target.x = event.x - target.offsetX
        target.y = event.y - target.offsetY
        
        -- Update the connected lines
        local i = circleIndex
        local numCircles = #polyData.circles
        
        local lineIndex1 = (i == 1) and numCircles or (i - 1)
        local line1 = polyData.lines[lineIndex1]
        if line1 then
            local c1 = polyData.circles[lineIndex1]
            local c2 = polyData.circles[i]
            line1:removeSelf()
            line1 = display.newLine(c1.x, c1.y, c2.x, c2.y)
            line1.strokeWidth = 2
            line1:setStrokeColor(1, 1, 0)
            polyData.lines[lineIndex1] = line1
        end
        
        local lineIndex2 = i
        local line2 = polyData.lines[lineIndex2]
        if line2 then
            local c1 = polyData.circles[i]
            local c2 = (i == numCircles) and polyData.circles[1] or polyData.circles[i+1]
            line2:removeSelf()
            line2 = display.newLine(c1.x, c1.y, c2.x, c2.y)
            line2.strokeWidth = 2
            line2:setStrokeColor(1, 1, 0)
            polyData.lines[lineIndex2] = line2
        end
        
    elseif event.phase == "ended" or event.phase == "cancelled" then
        if target.isFocus then
            display.currentStage:setFocus(nil)
            target.isFocus = false
            
            -- Update polyData.points with new positions, then push to editHistory
            local i = circleIndex
            polyData.points[(i*2) - 1] = target.x
            polyData.points[(i*2)]     = target.y
            
            table.insert(polyData.editHistory, clonePointsTable(polyData.points))
        end
    end
    
    return true
end

-- UNDO MOVEMENT IN EDIT MODE
--------------------------------------------------------------------------------
local function undoEdit(polygonIndex)
    local polyData = polygonsData[polygonIndex]
    if not polyData or not polyData.isInEditMode then return end
    
    if #polyData.editHistory < 2 then return end
    
    table.remove(polyData.editHistory, #polyData.editHistory)  -- discard current state
    
    local prevState = polyData.editHistory[#polyData.editHistory]
    if not prevState then return end
    
    -- Restore points
    polyData.points = clonePointsTable(prevState)
    
    -- Update circle positions
    for i, circle in ipairs(polyData.circles) do
        local px = polyData.points[(i*2) - 1]
        local py = polyData.points[(i*2)]
        circle.x = px
        circle.y = py
    end
    
    -- Update lines
    local numCircles = #polyData.circles
    for i, line in ipairs(polyData.lines) do
        line:removeSelf()
        local c1 = polyData.circles[i]
        local c2 = (i == numCircles) and polyData.circles[1] or polyData.circles[i+1]
        local newLine = display.newLine(c1.x, c1.y, c2.x, c2.y)
        newLine.strokeWidth = 2
        newLine:setStrokeColor(1, 1, 0)
        polyData.lines[i] = newLine
    end
end

-- ENTER / FINALIZE EDIT MODE
--------------------------------------------------------------------------------
function enterEditMode(polygonIndex)
    local polyData = polygonsData[polygonIndex]
    if not polyData or not polyData.polygonCreated then return end
    
    -- If another polygon is in edit mode, finalize it first
    if editModePolygonIndex and editModePolygonIndex ~= polygonIndex then
        finalizeEditMode(editModePolygonIndex)
    end
    
    -- If we had a polygon in move mode, stop moving it
    if movePolygonIndex and movePolygonIndex ~= polygonIndex then
        stopMovingPolygon(movePolygonIndex)  -- (NEW) function below
    end
    
    -- Remove final polygon display
    if polyData.finalPolygon then
        display.remove(polyData.finalPolygon)
        polyData.finalPolygon = nil
    end
    
    polyData.circles = {}
    polyData.lines   = {}
    
    for i = 1, #polyData.points, 2 do
        local x = polyData.points[i]
        local y = polyData.points[i+1]
        local circle = display.newCircle(x, y, 5)
        circle:setFillColor(1, 0, 0)
        circle.circleIndex  = (i+1)/2
        circle.polygonIndex = polygonIndex
        
        circle:addEventListener("touch", onCircleDrag)
        
        table.insert(polyData.circles, circle)
    end
    
    -- Reconnect lines
    for i = 1, #polyData.circles do
        if i > 1 then
            local prev = polyData.circles[i-1]
            local curr = polyData.circles[i]
            local line = display.newLine(prev.x, prev.y, curr.x, curr.y)
            line.strokeWidth = 2
            line:setStrokeColor(1, 1, 0)
            table.insert(polyData.lines, line)
        end
    end
    -- Close the loop
    if #polyData.circles >= 2 then
        local first = polyData.circles[1]
        local last  = polyData.circles[#polyData.circles]
        local line = display.newLine(last.x, last.y, first.x, first.y)
        line.strokeWidth = 2
        line:setStrokeColor(1, 1, 0)
        table.insert(polyData.lines, line)
    end
    
    polyData.isInEditMode = true
    editModePolygonIndex = polygonIndex
    
    polyData.editHistory = {}
    table.insert(polyData.editHistory, clonePointsTable(polyData.points))
end

function finalizeEditMode(polygonIndex)
    local polyData = polygonsData[polygonIndex]
    if not polyData or not polyData.isInEditMode then return end
    
    -- Gather updated points from circles
    polyData.points = {}
    for i, circle in ipairs(polyData.circles) do
        table.insert(polyData.points, circle.x)
        table.insert(polyData.points, circle.y)
    end
    
    -- Remove circles and lines
    for i=#polyData.circles,1,-1 do
        display.remove(polyData.circles[i])
        polyData.circles[i] = nil
    end
    for i=#polyData.lines,1,-1 do
        display.remove(polyData.lines[i])
        polyData.lines[i] = nil
    end
    
    polyData.isInEditMode = false
    editModePolygonIndex  = nil

    polyData.polygonCreated = false
    createPolygon(polygonIndex)
end

-- MOVE MODE
--------------------------------------------------------------------------------

-- HIGHLIGHT / UNHIGHLIGHT POLYGON 

local function highlightPolygon(polygonIndex)
    local polyData = polygonsData[polygonIndex]
    if polyData and polyData.finalPolygon then
        -- Example: yellow outline, thicker width
        polyData.finalPolygon:setStrokeColor(1, 1, 0)
        polyData.finalPolygon.strokeWidth = 3
    end
end

local function unhighlightPolygon(polygonIndex)
    local polyData = polygonsData[polygonIndex]
    if polyData and polyData.finalPolygon then
        -- Revert to the original green outline
        polyData.finalPolygon:setStrokeColor(0, 0.5, 0)
        polyData.finalPolygon.strokeWidth = 1
    end
end

local function onPolygonDrag(event)
    local poly = event.target  -- finalPolygon display object
    if not poly or not poly.polygonIndex then return false end
    
    local polyData = polygonsData[poly.polygonIndex]
    if not polyData or not polyData.finalPolygon then return false end
    
    if event.phase == "began" then
        display.currentStage:setFocus(poly)
        poly.isFocus = true
        
        -- Record the starting position of the polygon
        poly.startX = poly.x
        poly.startY = poly.y
        
        -- Also record the offset to the touch point
        poly.offsetX = event.x - poly.x
        poly.offsetY = event.y - poly.y
        
    elseif event.phase == "moved" and poly.isFocus then
        -- Update the polygon’s position
        poly.x = event.x - poly.offsetX
        poly.y = event.y - poly.offsetY
        
    elseif (event.phase == "ended" or event.phase == "cancelled") and poly.isFocus then
        display.currentStage:setFocus(nil)
        poly.isFocus = false
        
        -- Compute how far the polygon moved
        local dx = poly.x - poly.startX
        local dy = poly.y - poly.startY
        
        -- Apply this offset to polyData.points so that if we enter edit mode,
        -- the polygon is at its new location.
        for i = 1, #polyData.points, 2 do
            polyData.points[i]   = polyData.points[i]   + dx
            polyData.points[i+1] = polyData.points[i+1] + dy
        end
    end
    
    return true
end

local function startMovingPolygon(newIndex)
    local polyData = polygonsData[newIndex]
    if not polyData or not polyData.finalPolygon then return end
    
    -- If another polygon was being moved, stop moving it first
    if movePolygonIndex and movePolygonIndex ~= newIndex then
        unhighlightPolygon(movePolygonIndex)
        stopMovingPolygon(movePolygonIndex)
    end
    
    -- Set the new polygon index
    movePolygonIndex = newIndex
    
    -- Highlight this polygon’s outline
    highlightPolygon(newIndex)
    
    -- Add a "touch" listener to the final polygon for dragging
    polyData.finalPolygon:addEventListener("touch", onPolygonDrag)
end

function stopMovingPolygon(polygonIndex)
    local polyData = polygonsData[polygonIndex]
    if not polyData or not polyData.finalPolygon then return end
    
    -- (NEW) Remove highlight
    unhighlightPolygon(polygonIndex)
    
    -- Remove the listener
    polyData.finalPolygon:removeEventListener("touch", onPolygonDrag)
    movePolygonIndex = nil
end

-- TAP ON A FINAL POLYGON
--------------------------------------------------------------------------------
function onFinalPolygonTap(event)
    local tappedPolygon = event.target
    if not tappedPolygon or not tappedPolygon.polygonIndex then 
        return true 
    end
    
    local newIndex = tappedPolygon.polygonIndex
    
    -- If we are in "edit" mode, we want to enterEditMode
    if currentMode == "edit" then
        if editModePolygonIndex and editModePolygonIndex ~= newIndex then
            finalizeEditMode(editModePolygonIndex)
        end
        enterEditMode(newIndex)
        
    elseif currentMode == "move" then
        -- (NEW) If we tap a polygon in move mode, we want to start moving it
        startMovingPolygon(newIndex)
        
    else
        -- "create" mode => ignore taps on finalized polygons
    end
    
    return true
end

-- TAP FOR CREATING NEW POINTS (only in CREATE mode)
--------------------------------------------------------------------------------
local function onTapForCreation(event)
    if event.phase == "began" then
        
        -- Only proceed if in CREATE mode and not editing/moving
        if currentMode ~= "create" then
            return true
        end
        
        if editModePolygonIndex then
            return true
        end
        
        if movePolygonIndex then
            return true
        end
        
        local polyData = polygonsData[currentPolygonIndex]
        if not polyData or polyData.polygonCreated then
            return true
        end
        
        local x, y = event.x, event.y
        
        -- If at least 3 circles, check if tapping near the first circle
        if #polyData.circles >= 3 then
            local firstCircle = polyData.circles[1]
            if isNear(firstCircle.x, firstCircle.y, x, y, 10) then
                finalizeCurrentPolygonAndStartNew()
                return true
            end
        end
        
        -- Otherwise, create a new circle
        local circle = display.newCircle(x, y, 5)
        circle:setFillColor(1, 0, 0)
        table.insert(polyData.circles, circle)
        
        table.insert(polyData.points, x)
        table.insert(polyData.points, y)
        
        if #polyData.circles > 1 then
            local prevCircle = polyData.circles[#polyData.circles - 1]
            local line = display.newLine(prevCircle.x, prevCircle.y, x, y)
            line.strokeWidth = 2
            line:setStrokeColor(1, 1, 0)
            table.insert(polyData.lines, line)
        end
    end
    return true
end

-- KEYBOARD HANDLER
--------------------------------------------------------------------------------
local function onKeyEvent(event)
    if event.phase == "up" then
        
        if event.keyName == "space" and currentMode == "create" then
            finalizeCurrentPolygonAndStartNew()
        
        elseif event.keyName == "z" and event.isCtrlDown then
            if editModePolygonIndex then
                undoEdit(editModePolygonIndex)
            else
                local polyData = polygonsData[currentPolygonIndex]
                if polyData and not polyData.polygonCreated then
                    if #polyData.circles > 0 then
                        display.remove(polyData.circles[#polyData.circles])
                        polyData.circles[#polyData.circles] = nil
                        
                        if #polyData.lines > 0 then
                            display.remove(polyData.lines[#polyData.lines])
                            polyData.lines[#polyData.lines] = nil
                        end
                        
                        if #polyData.points >= 2 then
                            table.remove(polyData.points)
                            table.remove(polyData.points)
                        end
                    end
                end
            end
        
        elseif event.keyName == "escape" then
            if editModePolygonIndex then
                finalizeEditMode(editModePolygonIndex)
            end
            if movePolygonIndex then
                stopMovingPolygon(movePolygonIndex)
            end
        
        elseif (event.keyName == "delete" or event.keyName == "backspace") then
            if editModePolygonIndex then
                deletePolygon(editModePolygonIndex)
                editModePolygonIndex = nil
            elseif movePolygonIndex then
                deletePolygon(movePolygonIndex)
                movePolygonIndex = nil
            end
        end
    end
    
    return false
end

-- BUTTONS FOR CREATE vs EDIT vs MOVE
--------------------------------------------------------------------------------

local function setMode(newMode)
    -- If we leave "edit" mode, finalize
    if editModePolygonIndex and newMode ~= "edit" then
        finalizeEditMode(editModePolygonIndex)
    end
    
    -- If we leave "move" mode, stop moving
    if movePolygonIndex and newMode ~= "move" then
        stopMovingPolygon(movePolygonIndex)
    end
    
    currentMode = newMode
    
    -- Update button colors (unchanged logic for the 3-button setup)
    if currentMode == "create" then
        createButton:setFillColor(0.3, 0.8, 0.3)
        editButton:setFillColor(0.5, 0.5, 0.5)
        moveButton:setFillColor(0.5, 0.5, 0.5)
    elseif currentMode == "edit" then
        createButton:setFillColor(0.5, 0.5, 0.5)
        editButton:setFillColor(0.3, 0.8, 0.3)
        moveButton:setFillColor(0.5, 0.5, 0.5)
    else -- "move"
        createButton:setFillColor(0.5, 0.5, 0.5)
        editButton:setFillColor(0.5, 0.5, 0.5)
        moveButton:setFillColor(0.3, 0.8, 0.3)
    end
end

local function onCreateButtonTap(event)
    if event.phase == "ended" then
        setMode("create")
    end
    return true
end

local function onEditButtonTap(event)
    if event.phase == "ended" then
        setMode("edit")
    end
    return true
end

-- Move Button
local function onMoveButtonTap(event)
    if event.phase == "ended" then
        setMode("move")
    end
    return true
end

-- DRAW THE "CREATE", "EDIT", and "MOVE" BUTTONS
--------------------------------------------------------------------------------
createButton = display.newRoundedRect(display.contentCenterX - 130, 40, 80, 40, 10)
createButton:setFillColor(0.3, 0.8, 0.3)
createButton.strokeWidth = 2
createButton:setStrokeColor(1, 1, 1)
createButton:addEventListener("touch", onCreateButtonTap)
createButton:scale(0.7,0.7)

local createText = display.newText({
    text = "Create",
    x = createButton.x,
    y = createButton.y,
    fontSize = 32
})
createText:scale(0.5,0.5)

editButton = display.newRoundedRect(display.contentCenterX - 60, 40, 80, 40, 10)
editButton:setFillColor(0.5, 0.5, 0.5)
editButton.strokeWidth = 2
editButton:setStrokeColor(1, 1, 1)
editButton:addEventListener("touch", onEditButtonTap)
editButton:scale(0.7,0.7)

local editText = display.newText({
    text = "Edit",
    x = editButton.x,
    y = editButton.y,
    fontSize = 32
})
editText:scale(0.5,0.5)

-- "Move" button
moveButton = display.newRoundedRect(display.contentCenterX + 10, 40, 80, 40, 10)
moveButton:setFillColor(0.5, 0.5, 0.5)
moveButton.strokeWidth = 2
moveButton:setStrokeColor(1, 1, 1)
moveButton:addEventListener("touch", onMoveButtonTap)
moveButton:scale(0.7,0.7)

local moveText = display.newText({
    text = "Move",
    x = moveButton.x,
    y = moveButton.y,
    fontSize = 32
})
moveText:scale(0.5,0.5)

-- DELETE BUTTON
--------------------------------------------------------------------------------

local function onDeleteButtonTap(event)
    if event.phase == "ended" then
        if editModePolygonIndex then
            deletePolygon(editModePolygonIndex)
            editModePolygonIndex = nil
        elseif movePolygonIndex then
            deletePolygon(movePolygonIndex)
            movePolygonIndex = nil
        end
    end
    return true
end

deleteButtonRect = display.newRoundedRect(display.contentCenterX + 90, 40, 100, 40, 10)
deleteButtonRect:setFillColor(1, 0.2, 0.2)
deleteButtonRect.strokeWidth = 2
deleteButtonRect:setStrokeColor(1, 1, 1)
deleteButtonRect:addEventListener("touch", onDeleteButtonTap)
deleteButtonRect:scale(0.7,0.7)

deleteButtonText = display.newText({
    text = "Delete",
    x = deleteButtonRect.x,
    y = deleteButtonRect.y,
    fontSize = 32
})
deleteButtonText:scale(0.5,0.5)

-- The Export Function
--------------------------------------------------------------------------------


local function exportPolygonsAsSolar2DCode(filename)
    -- Open a file in system.DocumentsDirectory for writing
    local path = system.pathForFile(filename, system.DocumentsDirectory)
    local file, err = io.open(path, "w")
    if not file then
        print("Error opening file for write:", err)
        return
    end

    -- Write a header
    file:write("-- This file was automatically generated by the polygon tool.\n")
    file:write("-- Requiring this file returns a module table with .createPolygons() function.\n\n")
    
    -- Module start
    file:write("local M = {}\n\n")
    
    -- We'll define a function that, when called, creates all polygons and returns them in a table
    file:write("function M.createPolygons()\n")
    file:write("    local polygons = {}\n\n")
    
    -- Iterate over polygonsData
    for index, polyData in pairs(polygonsData) do
        -- Skip empty or deleted entries
        if polyData and #polyData.points >= 6 then

            -- Calculate bounding box and offsets, just like createPolygon()
            ------------------------------------------------------------------
            local minX, minY = math.huge, math.huge
            local maxX, maxY = -math.huge, -math.huge
            for i = 1, #polyData.points, 2 do
                local x = polyData.points[i]
                local y = polyData.points[i+1]
                if x < minX then minX = x end
                if y < minY then minY = y end
                if x > maxX then maxX = x end
                if y > maxY then maxY = y end
            end
            
            local width  = maxX - minX
            local height = maxY - minY
            local offsetX = minX + width * 0.5
            local offsetY = minY + height * 0.5

            -- Build centered coordinates for display.newPolygon
            -------------------------------------------------------
            local centeredCoords = {}
            for i = 1, #polyData.points, 2 do
                local x = polyData.points[i]   - offsetX
                local y = polyData.points[i+1] - offsetY
                table.insert(centeredCoords, x)
                table.insert(centeredCoords, y)
            end

            -- Now we write out the code for this polygon
            file:write("    do\n")
            file:write("        -- Polygon #"..tostring(index).."\n")
            file:write("        local centerX, centerY = "..offsetX..", "..offsetY.."\n")
            
            -- Write the coords array
            file:write("        local coords = {")
            for i, val in ipairs(centeredCoords) do
                file:write(val)
                if i < #centeredCoords then
                    file:write(", ")
                end
            end
            file:write("}\n")
            
            -- Now the code that actually creates the polygon
            file:write("        local poly = display.newPolygon(centerX, centerY, coords)\n")
            file:write("        poly:setFillColor(0.2, 0.7, 0.2, 0.4)\n")
            file:write("        poly.strokeWidth = 1\n")
            file:write("        poly:setStrokeColor(0, 0.5, 0)\n")
            
            -- Insert it into the polygons table
            file:write("        table.insert(polygons, poly)\n")
            file:write("    end\n\n")
        end
    end
    
    -- End of createPolygons
    file:write("    return polygons\n")
    file:write("end\n\n")
    
    -- Return the module
    file:write("return M\n")
    
    io.close(file)
    print("Exported polygons to:", path)
end

-- Create an "Export" button
local exportButtonRect = display.newRoundedRect(display.contentCenterX + 180, 40, 100, 40, 10)
exportButtonRect:setFillColor(0.2, 0.4, 1)  -- some distinct color
exportButtonRect.strokeWidth = 2
exportButtonRect:setStrokeColor(1, 1, 1)
exportButtonRect:scale(0.7,0.7)

local exportButtonText = display.newText({
    text = "Export",
    x = exportButtonRect.x,
    y = exportButtonRect.y,
    fontSize = 16*2
})
exportButtonText:scale(0.5,0.5)

-- Add tap listener
local function onExportButtonTap(event)
    if event.phase == "ended" then
        -- We'll define this function below
        exportPolygonsAsSolar2DCode("polygons_export.lua")
    end
    return true
end

-- EVENT LISTENERS FOR TAP AND KEY
--------------------------------------------------------------------------------
Runtime:addEventListener("mouse", onTapForCreation)  -- Desktop
Runtime:addEventListener("touch", onTapForCreation)  -- Mobile
Runtime:addEventListener("key", onKeyEvent)
exportButtonRect:addEventListener("touch", onExportButtonTap)
5 Likes

Nice work! Thank you for sharing it with us.

Do you have it on Github, GitLab or any other place so I can add this to the awesome-solar2d collection?