The International Simutrans Forum

Development => Bug Reports => Topic started by: victor_18993 on July 20, 2026, 10:34:57 AM

Title: Trees show through elevated ways: the clip is disabled by an integer division
Post by: victor_18993 on July 20, 2026, 10:34:57 AM
Václav mentioned in topic 23991 (https://forum.simutrans.com/index.php/topic,23991.0.html) that trees also appear under elevated ways. I looked into it, and the interesting part is that the engine already intends to prevent this.
simplan.cc even says so in its own comment — "clip horizontally to prevent trees etc shining trough bridges" — and obj_t::baum is handled explicitly. So this is not a missing feature. It is an existing case that currently does nothing.
What happens
The decision compares max_height with (htop - hmin), both expressed in height levels. However, max_height is calculated like this:
clip_h = rw/2 + (pak_height_conversion_factor * rw / 8);
 max_height = max(max_height, (area.h - area.y) / clip_h);
 if (max_height == 0) { /* display without clipping */ }
clip_h is 96 for pak128 and 40 for pak64. One height level on screen is:
tile_raster_scale_y(TILE_HEIGHT_STEP, rw)That is 16 pixels in both paksets.
So the divisor is six times too large in pak128 and two and a half times too large in pak64. The integer division usually rounds to zero, and clipping is skipped entirely.
Prissi's height_clearance sketch in topic 23991 also divides by height_step for essentially the same pixels-to-levels conversion, which is the unit I am arguing for here.
Measured from the paksets
I read the image dimensions directly from the paksets:
So this is not unusual artwork in one pakset.
It is not a pakset opting out either. I checked the WAY nodes, and all 22 elevated ways in pak128 declare clip_below=1. The only exception is Suspended_Monorail_Track, which correctly uses 0.
Why the case is reachable
A way built at ground level replaces the tile and removes the tree with it.
An elevated way leaves the kartenboden untouched and creates a monorailboden above it, so the tree remains underneath. On an empty pak128 map, 12 trees under a ground road become 0, while 12 trees under an elevated way remain 12.
tree_builder.cc also has no overhead check, so trees can be planted underneath an existing deck.
The patch
The patch is nine lines in one file: divide by the height of one level.
That also gives max_height a consistent meaning: height in levels, which is the same unit used by the comparison. max_height == 0 then correctly describes an object shorter than one height level. That includes roughly half of pak64's trees, which should not need clipping.
The clipping line itself is derived from the upper ground's height and does not use max_height. This therefore cannot make the clipping more aggressive than intended; it only prevents the existing clipping path from being skipped.
Test suite: 201/201.
A question I could not answer, and deliberately did not patch
This improves the result considerably, but it does not remove every tree. In pak128, bridges go from being covered in trees to leaving two or three visible.
Those remaining cases come from the numerator. (area.h - area.y) is negative for 144 of the 550 tree images, which cannot represent a height.
If an object stands on the ground line, then y + h is that line and the visible overhang is simply h. By contrast, h - y becomes 2h - ground_line, which is negative for many medium-height objects.
Replacing the numerator with h reduces the remaining cases from 32% to 1.6%.
I deliberately left that out of the patch. The divisor is a clear unit mismatch that can be justified independently. The numerator is my interpretation of the original intention, and I would rather ask than tune it until the screenshots look right.
So the question is: was (area.h - area.y) really the intended value here?
Prissi, your height_clearance sketch derives the height from ymin rather than subtracting a y offset from the image height. If ymin is the correct way to determine how tall a building sprite is at load time, it may also be the correct way to determine the height of a tree at draw time. That would make the second part less speculative, but I did not want to change it based only on that assumption.
Two more things I could not establish:
Title: Re: Trees show through elevated ways: the clip is disabled by an integer division
Post by: prissi on July 20, 2026, 12:12:22 PM
Trees could have an dx/dy offset, which is why they need extra clipping, they could appear high on a tile so any single height tree could shine through if located at the top corner of a tile.
I think this is what the check was originally bound to achieve. But the directional clipping routines are a little compolex and mostly from Dwachs, so I have no intimate knowledge either.
Title: Re: Trees show through elevated ways: the clip is disabled by an integer division
Post by: victor_18993 on July 21, 2026, 09:49:08 PM
You were right about the offset, and chasing it down turned up something bigger.

1. The offset. A tree carries a per-instance offset - baum_t::calc_off()
sets yoff = (y - x)/2 - zoff - and a negative yoff lifts it toward the top corner.
The draw path applies it scaled (simobj.cc: ypos += tile_raster_scale_y(get_yoff(),
rw)), but the clip estimate read only the sprite box. Fixed by folding the same
scaled yoff in, tree branch only:

const sint16 yoff = tile_raster_scale_y(o->get_yoff(), rw);
max_height = max(max_height, max(0, rw/2 - area.y - yoff) / level_h);

2. But the clip cannot hide a tall object at all. I built a testbed - a
height-1 deck over a grown city, with a monument and big trees under it - and
instrumented the clip. The decision now fires correctly: the deck is seen above,
is_clipping_below_needed() is true, max_height is measured, push_clip_rect runs
with yh = ypos + 80. And yet the monument and the trees draw whole. An A/B with the
push disabled is identical; forcing the clip to the extreme (cut the whole tile)
still leaves them whole. The reason is the one you hinted at: the clip rect wraps
only this tile's own display_obj_all, but a tall object's upper part is drawn during
a FRONT tile's pass, which the clip never wraps. So the divisor/numerator/offset
fixes make the decision correct and help the short things the clip can reach, but
clipping simply cannot cut a tall tree or monument poking above a low deck. That is
the Dwachs directional-clip territory you mentioned, and I did not want to rework it
blind.

3. So the real fix is placement, the same as 23991: don't let a tall thing get
under a deck in the first place.
Your renovation limit does this for city
buildings. I factored your clearance loop into the object that owns the ground
stack and applied it where a tall object still slipped under:

// planquadrat_t: smallest positive gap to a ground above the kartenboden,
// 127 if nothing overhead - your renovate loop, now shared.
sint16 planquadrat_t::get_overhead_clearance() const;

- City buildings, as a numeric limit (your steer). renovate_city_building
  already fed it; I added the same to build_city_building, so first placement is
  limited too, not just renovation - a city expanding under a deck was still putting
  a full-height building there on the first build. A building fits if its
  get_height_clearance() <= the clearance; raise the deck and taller ones fit.
- Monuments (monument_placefinder_t::is_tile_ok): kept off overhead tiles. I
  first made this the same numeric test, but a monument's unusual art makes its
  auto-detected height unreliable - a tall statue reported a small height and was
  placed under a low deck, poking. A monument is a showcase object that belongs on
  open ground anyway, so it is simplest and safe to keep it off deck tiles outright.
- Trees (both plant_tree_on_coordinate overloads: tool + forest + fill + spawn):
  not planted under a deck.
A tree carries no height metadata, and the clip
  can't rescue one that pokes, so it is not placed there. (A numeric test would need
  a tree height, which does not exist yet - happy to add it if you want the same
  "does it fit" logic for trees.)

Verified on pak128 under a height-1 deck: tree planting drops 12 tiles -> 0, the
tallest thing the city puts under the deck falls from a level-16 monument to a
level-5 shop, monuments relocate to open plazas and draw in full. Upstream suite
201/201.

Left for your call, deliberately not touched:
- Elevated way built straight OVER existing trees (wegbauer). The placement
  guard only stops new planting; a way built over a tree leaves it. A ground way
  already removes the tree it replaces - an elevated one could do the same under its
  deck, but that changes what the player sees when building, so it is yours to decide.
- Townhalls and attractions. The same "tall building under a deck" is possible
  via the townhall placefinder and the shared attraction placefinder, but I left
  them: a townhall is essential (refusing its tile could leave a city without one),
  and the attraction finder is shared infrastructure I did not want to change for one
  caller. Both are rare (a deck across a city centre). Flagging rather than guessing.

Numbers and the full instrumentation are in the attached document.