[FS19 Tutorial] increase the DensityHeighttypes/dumpable piles

disturbed_farmer
Posts: 11
Joined: Thu Jul 29, 2021 3:29 pm

Re: [FS19 Tutorial] increase the DensityHeighttypes/dumpable piles

Post by disturbed_farmer »

If you're using these instructions now I have found something confusing. If I use the latest version of GE I get an error loading the PNG. If I use an older version (8.1.0) then I have no issues and it all works perfectly.

Something to be aware of
buck2202
Posts: 5
Joined: Tue Sep 15, 2020 10:58 pm

Re: [FS19 Tutorial] increase the DensityHeighttypes/dumpable piles

Post by buck2202 »

(similar post for multi-terrain angle here: viewtopic.php?f=895&t=140397&p=1415800#p1415800)

I wanted to convert Alien Jim's Spectacle Island for increased height types, but felt like it was a shame to lose the mess that he so thoughtfully scattered for us at the farm (or worse, have it be messed up textures without collisions...). I started digging into the bit-level meanings in the terrainHeightDetail_density map, and wrote a little script to preserve heaps on the ground when you increase the height types

Crudely, a pixel in the density map of a 31-type map has values

Code: Select all

AAAAABBB|BBBBBBBB|BBBBBBBB
red     |green   |blue
where the 5 bits labeled "A" encode which filltype is on the ground at a given pixel. The rest of the bits aren't really important to understand this process, but at least specify the pile height.

If we add one channel in groundHeightShader.xml and the map i3d file to move to 63 height types, and then load an fewer-channel png in giants editor, the data is read through our new bitmask

Code: Select all

AAAAAABB|BBBBBBBB|BBBBBBBB
but since the data wasn't written in that format, the leftmost bit of our old "B" data is interpreted as part of "A", and "B" is interpreted with its original leading bit missing and the rest shifted. This causes materials to differ from pixel to pixel within what was once a uniform heap, piles change height or disappear, and collisions are lost due to nonsense data.

To preserve the original heap types, you can insert a processing step between using the GRLE converter and loading in giants editor. When adding a single height type channel, we convert the data format to

Code: Select all

AAAAA0BB|BBBBBBBB|BBBBBBBB
just appending an appropriate number of zeros to the original "A" data, and shifting B to the right (dropping some thankfully-unused bits off the end).


I wrote a quick python script to do this, and thought I'd share it in case anyone else might find it useful. I wrote it to be used from the command line in linux, but the update_pixel function should be easily adaptable to other workflows.

Code: Select all

#!/usr/bin/python3

import sys,argparse
from os.path import exists
from PIL import Image

def update_pixel(p,inchan,outchan):
	if all(_p==0 for _p in p):
		return p
	pixel = p[0] + (256 * p[1]) + (65536 * p[2])
	
	intypemask = 2**inchan - 1
	outpixel = (
		(pixel & intypemask) | 								#mask/preserve inchan-bits corresponding to the heap type
		( (pixel & ~intypemask) << (outchan-inchan) ) )		#shift the rest to the left by the number of added height channels
	
	return (outpixel % 256, outpixel // 256, outpixel // 65536)


def main(argv):
	parser = argparse.ArgumentParser(description='Converts terrainDetailHeight density map between #types without heap type loss')
	
	parser.add_argument('-i', '--inchannels',	action='store', type=int, default=5,	dest='inchannels',					help='# height channels in input png. default=5-->31 heighttypes')
	parser.add_argument('-o', '--outchannels',	action='store', type=int, 				dest='outchannels', required=True,	help='# height channels in output png. end heighttypes=2^n-1')
	parser.add_argument('-I', '--inpng',		action='store', 						dest='infile',		required=True,	help='input png (output from GRLE converter)')
	parser.add_argument('-O', '--outpng',		action='store',							dest='outfile',		required=True,	help='file to write')
	args = parser.parse_args()
	
	print(args)
	print("in channels:", args.inchannels)
	print("out channels:", args.outchannels)
	print("in file:", args.infile)
	print("out file:", args.outfile)
	
	addchannels = args.outchannels - args.inchannels
	if not exists(args.infile):
		print("in file missing")
		sys.exit(1)
	elif exists(args.outfile):
		print("out file exists. won't overwrite")
		sys.exit(1)
	elif args.inchannels < 5:
		print("inchannels invalid")
		sys.exit(1)
	elif addchannels == 0:
		print("nothing to do")
		sys.exit(0)
	elif addchannels < 0:
		print("inchannels > outchannels. why?")
		sys.exit(1)
		
	
	img = Image.open(args.infile)
	pixels = list(img.getdata())
	new_pixels = [update_pixel(p,args.inchannels,args.outchannels) for p in pixels]
	img.putdata(new_pixels)
	img.save(args.outfile)
	
		
if __name__ == "__main__":
   main(sys.argv[1:])
My testing went as far as loading Spectacle Island with
  • Seasons
  • Straw Harvest
  • MaizePlus
  • MaizePlus Forage Extension
all enabled, and trying to load every pile I saw into a frontloader bucket. As far as I noticed, every pile made it through the conversion from 31 to 127 height types intact.

To be clear, you would use the grle converter as before, run this script on its output, and then copy this script's result into the mapDE or mapUS folder (all other steps still required). For maps with no starting heaps, there's no benefit to this extra processing step. But, hopefully others will find it useful for maps that do, or for trying to preserve their savegame.
Post Reply