PDA

View Full Version : Generating LOS via SVG from GIMP



muklin
October 25th, 2020, 03:58
Hi all,

I create a lot of my own maps, and wanted the ability to use GIMP to select the negative areas of the maps, and use that as the basis for my walls.
I know theres been some conversations about how to create LOS Walls, in the forums, but this is my own take on it.

The process is based around making a selection in GIMP.
40490

Convert that to a PATH, (Menu: Select-> To Path)


Now Export it as a SVG File.
40491

The SVG export from GIMP seems to be pretty specific SVG format, so I don't promise this works for all SVGs.
40492

Then run the below powershell script that, for all SVG files in a directory:
1. Gets the image size. (This is needed to convert to FGU's Frame of Reference: 0,0 at the centre, and orientation: Y axis reversed)
2. Parse the path coords, and storing them in a List.
3. Parse the List, removing doubles, (I extended this to reduce the number of points, by distance.)
4. Output the list to the same filename as the svg, in the XML Occluder format that FGU's db.xml expects.

Then you take that xml, find your Image Layer in your dbxml and put it in there.
40493

Done!!
40494


Note it only handles walls. But of course, you could just make selection paths for all the different features in gimp, and just toggle their type in the db.xml.

Hope this helps someone!!
Muklin

Here is the powershell:



param(
[String]$filePath = $PWD,
[Int]$SmoothDistance = 10
)

if(test-path -path $filePath){
$files = Get-ChildItem -name $filePath -Include *.svg
}

$files | %{
$file= $filePath+"\"+$_
$filename = $_ -replace ".svg",".occ"
Write-Output "Processing $file..."

if([System.IO.File]::Exists($file)){
$content = get-content $file
}else{
Write-Error "Could not find file $file"
exit 2
}

$matchResult = [regex]::Match($content,"viewBox=\`"0 0 ([0-9]+) ([0-9]+)\`"")
$xWide = $matchResult.Groups[1].Value
$yHigh = $matchResult.Groups[2].Value

Write-Host "Detected an image $xWide by $yHigh"

$xWideMod = $xWide/2
$yHighMod = $yHigh/2

"<occluders>" | Out-file -Filepath $filename
$occludercount = 0
$m = Select-String -InputObject $content -Pattern "C[0-9., `r`n]+Z" -AllMatches
$array = [System.Collections.ArrayList]@()
$m.Matches | %{
$line = $_.Value.trim("C").trim("Z")
$matching = Select-String -InputObject $line -Pattern "([0-9\.,]+)" -AllMatches

foreach($match in $matching.Matches){
$coord = $match.Value.split(",")
$coord[0] = $coord[0] -as [int]
$coord[1] = $coord[1] -as [int]

if($array.Count -gt 0 -and [Math]::Pow($array[-1][0]-$coord[0],2)+[Math]::Pow($array[-1][1]-$coord[1],2) -lt [Math]::Pow($SmoothDistance,2) ) {
#"discarded a point"
}else{
$array.Add($coord) | Out-Null
}
}
$out = ""
$array | %{
$modX = $_[0]-$xWideMod -as [String]
$modY = $yHighMod-$_[1] -as [String]
$out = $out+$modX+","+$modY+","
}

"`t<occluder>" | Out-file -append -Filepath $filename
"`t`t<id>$occludercount</id>" | Out-file -append -Filepath $filename
"`t`t<points>$($out.trim(","))</points>" | Out-file -append -Filepath $filename
"`t`t<closedpolygon>true</closedpolygon>" | Out-file -append -Filepath $filename
"`t</occluder>" | Out-file -append -Filepath $filename
$occludercount++
$array.RemoveRange(0,$array.Count)
}
"</occluders>" | Out-file -append -Filepath $filename
}

YAKO SOMEDAKY
October 25th, 2020, 14:47
I would like to take some questions:
1 - Can I do this type of export only with GIMP?
2 - Are the colors defined by the Pen Tool that define the type of LoS or not?
3 - Where would that Script be used at the end?

muklin
October 26th, 2020, 22:06
1. The workflow I described is only tested using Gimp to create an SVG. Potentially, some other SVGs would work, but probably not. Potentially even other versions of GIMP wouldn't work.
2. This is for FGU. See the LOS videos you can find linked in various places on this forum to see how LOS in FGU works.
3. The script converts the SVG to the occluder data, to be inserted into the db.xml file for your campaign. So you'd use it after you created the SVG. As mentioned in the post.

TheFabulousIronChef
October 27th, 2020, 13:32
This is cool, thanks. Any chance of a brief step by step video explaining it further for those familiar with GIMP but not the dbxml files? (I don’t even know what those are).

muklin
October 27th, 2020, 23:07
This is cool, thanks. Any chance of a brief step by step video explaining it further for those familiar with GIMP but not the dbxml files? (I don’t even know what those are).

db.xml is the main xml file that FG and FGU use to store the state of your campaign.

You can find the file by browsing to the Fantasy Grounds Data directory.
There are various links to this directory in the application, but also see:
https://www.fantasygrounds.com/wiki/index.php/Data_Files_Overview

Which says:
"The Fantasy Grounds data folder is specified in the FG Settings when installing FG.
The easiest way to access the data directory is to click the folder icon in the top right of the main FG startup screen. This will open a new file explorer window at the root of the data directory. "

Inside here, browse to campaigns/<your campaign name
db.xml will be in there, and is just a big ol xml file with the data in your campaign.

A few notes and disclaimers on working with db.xml
DISCLAIMER: All this information is what I've reverse engineered from using FG over the years. Take it with a grain of salt. It worked for me. I don't take responsibility for any loss of data.
1. Before anything else, BACKUP your current db.xml. If you make a mistake, then you might end up losing everything. Having a back up is always a good idea.
2. I recommend you only edit db.xml while your campaign is not loaded. This will save you heartache of making lots of edits in the file, only for FG to overwrite it when you make changes in the application.
2a. Alternatively, you can use the /reload and /save command lines in FG to force it to load changes.
3. Load the image you want to change into the campaign first, then close the campaign. this will create the image xml node in the db.xml.
4. Alter the image node to have, (or replace, or alter) the generated <occluders> sub node.
5. Save your file.
6. Reload your campaign.
7. Open the map and see if your occluders are set as you need them.
8. Rinse and repeat til you're happy.

jharp
September 8th, 2021, 19:31
A little of an old thread but still I had to say that this is an absolutely wonderful method of doing LoS. It works great.

A couple of additions I would add...

1. Use of a gaussian blur with the blending mode as color erase can help produce selections that inset the LoS into the wall slightly.
2. Use of the advanced selection to path can help with a little better granulation on the path.

I use this python-fu:

img = gimp.image_list()[0]
pdb.plug_in_sel2path_advanced(img,None,0.5,60.00,4 ,100.00,0.40,1,1.00,1,0.33,3,2,0,0.010,0.5,0.01,1, 0.1,4,0.09,3)

Hope this helps others with this method.

Jason

dimonic
February 20th, 2022, 23:43
I was inspired to try a bash (linux shell) version of this.



#!/bin/bash

if [ $# -lt 1 ]
then
echo "Usage:\n$ $0 <svg_file>..."
exit 1
fi

in_file="$1"
out_file=${in_file%.svg}.occ
vb_regex='viewBox="0 0 ([0-9]+) ([0-9]+)"'
smooth_distance=10

readarray content < "${in_file}"

if [[ "${content[@]}" =~ $vb_regex ]]
then
xWide=${BASH_REMATCH[1]}
yHigh=${BASH_REMATCH[2]}

echo "Detected an image $xWide by $yHigh"
fi

let xWideMod=$xWide/2
let yHighMod=$yHigh/2

echo "<occluders>" > "${out_file}"
export occludercount=0
oc_regex='C[0-9\., \
]+Z'
coord_regex='([0-9\.,])+'


close_to_last() {
if [ ${#array[@]} -eq 0 ]
then
return 0
fi
# echo "End of array: ${array[-1]}"
local lastx=${array[-1]%,*}
local lasty=${array[-1]/"$lastx,"/}
return $(( ( ($lastx - ${coord[0]}) ** 2 + ($lasty - ${coord[1]}) ** 2 ) < ( $smooth_distance ** 2 ) ))
}

export OUT=""
chunk="${content[@]}"
while [[ "$chunk" =~ $oc_regex ]]
do
group="${BASH_REMATCH[0]}"
# echo "$group"
(
declare -a array
declare -a coord
while [[ "$group" =~ $coord_regex ]]
do
t=${BASH_REMATCH[0]%,*}
coord[0]=${t%.*}
t=${BASH_REMATCH[0]/"$t,"/}
coord[1]=${t%.*}
# echo "Found: (${coord[0]},${coord[1]})"
close_to_last
if [ $? -eq 0 ]
then
array+=("${coord[0]},${coord[1]}")
# echo "Added: ${array[-1]}"
fi
group=${group/"${BASH_REMATCH[0]}"/}
done
if [ ${#array[@]} -gt 1 ]
then
for point in ${array[@]}
do
coord[0]=${point%,*}
coord[1]=${point/"${coord[0]},"/}
# echo -n "Processing (${coord[0]},${coord[1]})"
let modX=${coord[0]}-$xWideMod
let modY=$yHighMod-${coord[1]}
# echo " = ($modX,$modY)"
OUT="${OUT}${modX},${modY},"
done
out=${OUT%,}
cat <<HERE >> "$out_file"
<occluder>
<id>$occludercount</id>
<points>$out</points>
<closedpolygon>true</closedpolygon>
</occluder>
HERE
fi
unset array
)
occludercount=$((occludercount+1))
chunk=${chunk/"${BASH_REMATCH[0]}"/}
# echo -n "Press enter to continue"
# read enter
done
echo "</occluders>" >> "${out_file}"


This seems to produce a valid occluders file for me. Dunno if there are any other Linux users out there - but here it is.
Also on github: https://github.com/dimonic/fgu-scripts