fix(m110): use sprite-alpha occlusion and self-target exclusion
This commit is contained in:
@@ -1196,17 +1196,6 @@ func _visible_ui_ink_rects(shell: Control) -> Array:
|
||||
return rects
|
||||
|
||||
|
||||
func _visible_actor_rects(shell: Control) -> Array:
|
||||
var rects: Array = []
|
||||
for actor_group in ["BattleActors", "BattleEnemies"]:
|
||||
var group := shell.find_child(actor_group, true, false)
|
||||
if group == null:
|
||||
continue
|
||||
for actor in group.get_children():
|
||||
rects.append((actor as Control).get_global_rect().grow(1.0))
|
||||
return rects
|
||||
|
||||
|
||||
func _visible_text_nodes(node: Node, out: Array) -> void:
|
||||
for child in node.get_children():
|
||||
if (child is Label or child is BaseButton) and (child as CanvasItem).is_visible_in_tree():
|
||||
@@ -1214,6 +1203,50 @@ func _visible_text_nodes(node: Node, out: Array) -> void:
|
||||
_visible_text_nodes(child, out)
|
||||
|
||||
|
||||
func _actor_occlusion_masks(shell: Control) -> Array:
|
||||
# Sprite-space occlusion: a pixel counts as occluded only when the actor
|
||||
# portrait texture is actually opaque there (the art layer stays below the
|
||||
# actor layer, so the path is hidden only behind opaque sprite pixels).
|
||||
var masks: Array = []
|
||||
for group_name in ["BattleActors", "BattleEnemies"]:
|
||||
var group := shell.find_child(group_name, true, false)
|
||||
if group == null:
|
||||
continue
|
||||
for actor in group.get_children():
|
||||
for portrait_name in ["BattlePortrait", "BattleEnemyPortrait"]:
|
||||
var portrait := actor.find_child(portrait_name, true, false) as TextureRect
|
||||
if portrait == null or portrait.texture == null or not portrait.is_visible_in_tree():
|
||||
continue
|
||||
var sprite := portrait.texture.get_image()
|
||||
if sprite == null:
|
||||
continue
|
||||
masks.append({"rect": portrait.get_global_rect(), "image": sprite})
|
||||
return masks
|
||||
|
||||
|
||||
func _pixel_occluded(masks: Array, pixel: Vector2) -> bool:
|
||||
for mask_value in masks:
|
||||
var mask: Dictionary = mask_value
|
||||
var rect: Rect2 = mask.rect
|
||||
if not rect.has_point(pixel):
|
||||
continue
|
||||
var sprite: Image = mask.image
|
||||
var texture_size := Vector2(float(sprite.get_width()), float(sprite.get_height()))
|
||||
if texture_size.x <= 0.0 or texture_size.y <= 0.0:
|
||||
continue
|
||||
var scale := minf(rect.size.x / texture_size.x, rect.size.y / texture_size.y)
|
||||
if scale <= 0.0:
|
||||
continue
|
||||
var drawn := texture_size * scale
|
||||
var origin := rect.position + (rect.size - drawn) * 0.5
|
||||
var uv := (pixel - origin) / scale
|
||||
var sx := clampi(int(uv.x), 0, sprite.get_width() - 1)
|
||||
var sy := clampi(int(uv.y), 0, sprite.get_height() - 1)
|
||||
if sprite.get_pixel(sx, sy).a > 0.4:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _point_in_any_rect(rects: Array, point: Vector2) -> bool:
|
||||
for rect_value in rects:
|
||||
if (rect_value as Rect2).has_point(point):
|
||||
@@ -1263,12 +1296,12 @@ func _is_palette_blend(color: Color, stroke: Color, underlays: Array) -> bool:
|
||||
return false
|
||||
|
||||
|
||||
func _core_carrier_at(image: Image, point: Vector2, normal: Vector2, side: float, stroke: Color, underlays: Array, ui_rects: Array, actor_rects: Array) -> Dictionary:
|
||||
func _core_carrier_at(image: Image, point: Vector2, normal: Vector2, side: float, stroke: Color, underlays: Array, ui_rects: Array, occlusion_masks: Array) -> Dictionary:
|
||||
# Walk outward from the bright core and take the first non-chrome pixel:
|
||||
# with the A5 underlay in place this is the underlay; without it, the raw
|
||||
# rendered carrier is returned (so the measurement can still fail honestly).
|
||||
# A first valid pixel inside an actor portrait is a sprite occluder, not a
|
||||
# carrier: the point is reported as occluded and excluded (M110 A5 rule).
|
||||
# A first valid pixel covered by an opaque actor sprite is an occluder, not
|
||||
# a carrier: the point is reported as occluded and excluded (M110 A5 rule).
|
||||
for step in range(7):
|
||||
var distance := 1.6 + 0.5 * float(step)
|
||||
var probe := point + normal * distance * side
|
||||
@@ -1285,7 +1318,7 @@ func _core_carrier_at(image: Image, point: Vector2, normal: Vector2, side: float
|
||||
return {
|
||||
"color": color,
|
||||
"distance": distance,
|
||||
"occluded": _point_in_any_rect(actor_rects, pixel_position),
|
||||
"occluded": _pixel_occluded(occlusion_masks, pixel_position),
|
||||
}
|
||||
return {}
|
||||
|
||||
@@ -1329,6 +1362,8 @@ func _path_contrast(image: Image, shell: Control) -> Dictionary:
|
||||
var height := float(image.get_height())
|
||||
var source: Vector2 = art.get("source_anchor") * Vector2(width, height)
|
||||
var target: Vector2 = art.get("target_anchor") * Vector2(width, height)
|
||||
if source.distance_to(target) < 1.0:
|
||||
return {"status": "NOT_APPLICABLE", "reason": "self-target event: no relation span between distinct actors"}
|
||||
var bend := -height * 0.095 if source.x < target.x else height * 0.075
|
||||
var midpoint := (source + target) * 0.5 + Vector2(0.0, bend)
|
||||
var points: Array = []
|
||||
@@ -1336,7 +1371,7 @@ func _path_contrast(image: Image, shell: Control) -> Dictionary:
|
||||
var t := float(index) / 20.0
|
||||
points.append(source * pow(1.0 - t, 2.0) + midpoint * 2.0 * (1.0 - t) * t + target * pow(t, 2.0))
|
||||
var ui_rects := _visible_ui_ink_rects(shell)
|
||||
var actor_rects := _visible_actor_rects(shell)
|
||||
var occlusion_masks := _actor_occlusion_masks(shell)
|
||||
var measured: Array = []
|
||||
var skipped := 0
|
||||
var occluded := 0
|
||||
@@ -1346,7 +1381,7 @@ func _path_contrast(image: Image, shell: Control) -> Dictionary:
|
||||
var normal := Vector2(-tangent.y, tangent.x)
|
||||
var point_carriers: Array = []
|
||||
for side in [-1.0, 1.0]:
|
||||
var entry := _core_carrier_at(image, point, normal, side, stroke, underlays, ui_rects, actor_rects)
|
||||
var entry := _core_carrier_at(image, point, normal, side, stroke, underlays, ui_rects, occlusion_masks)
|
||||
if entry.is_empty():
|
||||
continue
|
||||
if bool(entry.get("occluded", false)):
|
||||
|
||||
Reference in New Issue
Block a user