lib_wed function library

Dependencies: lib_2da, lib_anon, lib_array, lib_fn, lib_ini, lib_sfo, lib_struct, lib_sugar, lib_tools

Functions from unidentified sources: edit_area

Description

Library for editing WED files, mostly for cropping and reflecting areas and copying polygons from one to another.

THIS IS NOT FULLY FUNCTIONAL atm - one function needs to be SFOv2-ified. & best to test the whole thing on WR/LI.

Functions

wed_add_door(name:s, tiles:s)=() patch

Add a door to the current wed file. Supply the name (to match the are file) and a string of k=>v pairs identifying the tile maps when the door is closed

wed_build(width:i, height:i, tis:s, wed:s)=() action

Build a new, blank WED file of the specified height and width.

wed_crop_area(x:i, xlen:i, y:i, ylen:i, area_old:s, area_new:s)=() action

Copy an area, cropping it to the desired size (in tiles).

After doing it, you might want to rebuild the TIS in NI, as it's quite inefficiently stored in this algorithm

*Mostly* polygons need to be ordered with the lowest point first (as per IESDP). But this is NOT always correct and I can't work out the true algorithm.

EE only

wed_delete_polygons(area:s, polygons:s)=() action

Delete wall polygons by number.

wed_duplicate_area(area_old:s, area_new:s)=() action

Make an identical but differently-named copy of an area

wed_flip_area(area:s, file_loc:s, script_array:s)=() action

Flip an area along its vertical axis

You need to supply (in 'file_loc') a properly-named TIS file and associated PVRZ files, plus (for legacy use) a legacy TIS file with a 'v' name suffix, plus the height,search and light maps

DO NOT do the inversion in Paint, as it apparently scrambles transparency layers. Photoshop works.

You can also pass the program an array of scripts (in the form $[script_array]("[script]")="") whose coordinates need to be inverted.

We are probably assuming ARE v1.0 in some places.

wed_get_polygons(xmin:i, xmax:i, ymin:i, ymax:i, wed:s, output_file:s)=() action

Extract all the polygons in a specified region of a wed file.

wed_remap_search_map_colors(map:s, reference:s, map_out:s)=() action

Take a 4-bit spat out by Photoshop and remap it to the standard 4-bit SR palette. (Use L^2 distance in color space if no exact match)

(I have tried to add 8bit=>4bit to this, but it's erratic and not a priority to fix)

wed_swap_searchmap_code(old:i, new:i, map:s)=() action
wed_swap_searchmap_code(old:i, new:i)=() patch

??map IWD to BG2 searchmap?? NB not dimorphic

wed_transcribe_polygons(delta_x:i, delta_y:i, st, retch_numerator:i=1, st, retch_denominator:i=1, st, retch_base_x:i, st, retch_base_y:i, from_area:s, to_area:s, polygons:s)=() action

Transcribe the wall polygons with the associated numbers from one wed file to another, optionally adjusting their vertices and bounding boxes by delta and stretching around a base point.

Polygons must be listed sequentially.

Internal functions

These should not be called from outside the library itself.

wed_append_to_vertices(x:i, y:i, vertices:s)=(vertices:s) patch

Add a vertex to a string of vertices

wed_check_for_multiples()=(value:s) patch

Check if a WED file uses the multiple-tile functionality (used for things like flapping flags in Candlekeep and some fireplaces; not currently supported in wed_crop_area)

wed_clip_polygon_block(x:i, xlen:i, y:i, ylen:i, polygons_in:s, vertices_in:s)=(polygons_out:s, vertices_out:s) patch

wed_remap_this_tile

wed_count_wallgroups()=(value:s) patch

Find how many wallgroups a wed should have

wed_crop_are_file(x:i, y:i, xlen:i, ylen:i, area:s)=() action

Crop an area file of all out-of-bounds elements, and readjust other elements to the right coordinates

Requires SFOv1 functionality

wed_crop_doors(x:i, y:i, xlen:i, ylen:i)=() patch

Remove doors that are out of bounds and move the polygons of the rest.

wed_crop_polygons(x:i, xlen:i, y:i, ylen:i)=() patch

Crop the wall polygons

wed_deduplicate_vertices(vertices:s)=(vertices:s) patch

Given a string of vertices, remove consecutive duplicates from the string

wed_delete_wallgroups()=() patch

Delete all wallgroups and associated polygons

Here we assume that (i) the door polygons strictly follow the wall polygons (ii) the door polygons are in order (so the index of the first door polygon indicates where the door polygons start) (iii) the vertex indexes used by the door polygons strictly follow those used by the wall polygons (iv) the vertex indexes used by the door polygons again appear in order

wed_find_crossing_points(x1:i, x2:i, y1:i, y2:i, xmin:i, xmax:i, ymin:i, ymax:i)=(xcross1:s, ycross1:s, xcross2:s, ycross2:s) patch

Function to find when there is a crossing point on a boundary between two points. If there are two, return the closest one to the first point

wed_find_quadrant(x:i, xmin:i, xmax:i, y:i, ymin:i, ymax:i)=(quadrant:s) patch

Find what quadrant a point is in.

wed_flip_bounding_box(width_total:i, offset:i, layout:i)=() patch

Take a bounding box and flip it L/R Multiple layouts. 1=LRTB, 2=LTRB

wed_flip_orientation(offset:i)=() patch

Take an orientation and flip it L-R

wed_flip_tile(height:i, width:i, tile_number:i)=(new_number:s) patch

Take a tile and flip its index L-R

wed_flip_vertex(width_total:i, offset:i, is_searchmap:i)=() patch

Take a vertex and flip it L/R

wed_get_vertex(ind:i, vertices:s)=(x:s, y:s) patch

Get a vertex from a point in a string of vertices

wed_insert_polygon_block(polygons:s, vertices:s)=() patch

Given a block of polygons and another of vertices, insert then into a file as its wall group (destructive overwrite)

wed_invert_vertex_sequence_order(initial_vertex_offset:i, initial_vertex_index:i, vertex_number:i, width_total:i, is_searchmap:i)=() patch

Take the sequence of vertices defining a polygon. Flip each element, then reverse the order, from 0 1 2 3 ... N to 0 N ... 3 2 1

wed_l_r_crossing_point(x1:i, x2:i, y1:i, y2:i, bdy:i)=(xcross:s, ycross:s) patch

Function to find when the line between two points crosses the vertical line x=bdy

wed_order_wall_vertices(vertices:s)=(vertices:s) patch

A wall polygon needs to start with the lowest (=largest y value) term. Enforce this.

If there are two points that share a lowest value, I think they need to be the sequential. Normally that will occur automatically, but we need to allow for the first and last being joint lowest

wed_realign_vertices(x:i, y:i)=() patch

Move the vertices in to the top left corner (x,y are in tiles)

wed_realign_wall_polygon_bbs(x:i, y:i)=() patch

Move the wall-polygon bounding boxes in to the top left corner (x,y are in tiles)

wed_rebuild_wall_groups()=() patch

Rebuild wall groups i.e. go through the wall polygons and build the wall groups from scratch

wed_rectangle_overlap(xmin_1:i, xmax_1:i, ymin_1:i, ymax_1:i, xmin_2:i, xmax_2:i, ymin_2:i, ymax_2:i)=(overlap:s) patch

Check if two rectangles overlap

wed_remap_this_tile(x:i, xlen:i, y:i, ylen:i, height:i, width:i, tile_number:i)=(value:s) patch

wed_remove_tiles(x:i, y:i, xlen:i, ylen:i)=() patch

Remove tiles from a WED, on the assumption we're cropping.

wed_search_map_truncate(x:i, y:i, xlen:i, ylen:i, type:s)=() patch

Truncate a bitmap (search map, etc)

These are uncompressed grids of data in 4-bit per-pixel encoding (see IESDP), or 8-bit for the light map. Note that the bitmaps are stretched vertically compared to the displayed areas. There are 4 pixels per tile horizontally, 4*4/3 pixels per tile vertically

data begins at 0x36 +4* num_colors goes from the bottom left corner, not top left as per BG conventions

A complication is that in the uncompressed BMP format, data rows must be multiples of 4 bytes. That means that for the 4-bits-per-pixel maps (HT and SR), if xlen is an odd number the rows need to be null-padded.

wed_stretch_point(x:i, y:i, x_0:i, y_0:i, numerator:i=1, denominator:i=1)=(x:s, y:s) patch

Stretch a point away from a reference point.

wed_truncate_bitmap_row(row_num:i, xmin:i, ymin:i, xmax:i, ymax:i, divisor:i, row:s)=(row_out:s, row_empty:s) patch

wed_truncate_polygon(xmax:i, ymax:i, xmin:i, ymin:i, bb_ymax:i, vertices:s)=(vertices:s, bb_xmin:s, bb_xmax:s, bb_ymin:s, bb_ymax:s) patch

Truncate a polygon onto a rectangle (and also return its bounding box)

This is a non-trivial mathematical problem. Naive solutions end up getting confused when corners get cut off, or when a line between two points crosses the boundary twice.

Here's my solution:

  • work through consecutive pairs (P1,P2), copying them to a new file
  • Step 1:
    • if P1 is inside, add it
    • if P1 is outside, and in a face region (i.e. in bounds one way but not another, add a placeholder token (a 4-character string, 'xmin','ymax' etc)
    • if P1 is outside, and in a corner region (i.e. out of bounds in both ways) add the appropriate corner
  • Step 2: add any crossing point(s) between P1 and P2

  • Then go through again. Any transitions between faces get replaced by the associated corner

To complicate matters, polygons *mostly* need to be listed with the highest-y-value (i.e. lowest) point first. But this is not *always* true, and sometimes forcing the order breaks the polygon. What the actual rule is, I don't know, despite an afternoon's research.

So: the plan is, ain't broke don't fix. *If* the first polygon obeys the rule, *and* it gets truncated, then we'll reorder. Otherwise, not.

This generates duplicates, so deduplicate it at the end.

wed_truncate_wall(xmin:i, xmax:i, ymin:i, ymax:i, vertices:s)=(vertices:s, in_bounds:s) patch

Truncate the line defining a hovering wall baseline

wed_u_d_crossing_point(x1:i, x2:i, y1:i, y2:i, bdy:i)=(xcross:s, ycross:s) patch

Function to find when the line between two points crosses the horizontal line y=bdy

wed_update_bb(x:i, y:i, bb_xmin:i, bb_xmax:i, bb_ymin:i, bb_ymax:i)=(bb_xmin:s, bb_xmax:s, bb_ymin:s, bb_ymax:s) patch

update a bb so that it includes a point