HammeritePortal
Core: the map. Inherits Resource
A derived portal joining two rooms in the subtractive world model.
A portal exists between two air brushes when they share a face OR intersect; the portal is the boundary of the shared region. There is no separate stable portal id: a portal is identified by the unordered pair of air brush ids {A, B}, plus opening_index when the two touch in more than one place. Everything that references a portal (audio edge, per-portal override, lightmap adjacency) keys on that (key()), which survives a nudge - only the portal polygon changes shape. Side A is always the lower air brush id, and everything that has a side follows it: plane faces out of side A, and host_face is side A's face. Derived and rebuilt on every edit, never saved with a map. A Resource rather than a RefCounted like HammeriteBoundaryWall only because HammeriteCell is one too; the compile cache keeps neither as objects.
Properties
| Type | Name | Default |
|---|---|---|
int | air_brush_a | 0 |
int | air_brush_b | 0 |
int | region_a | 0 |
int | region_b | 0 |
int | opening_index | 0 |
PackedVector3Array | polygon | PackedVector3Array() |
Plane | plane | Plane(0, 0, 0, 0) |
HammeriteFace | host_face | null |
HammeriteFace | host_face_b | null |
Methods
| Returns | Method |
|---|---|
Vector3i | make_key(a: int, b: int, discriminator: int = 0) static |
Vector3i | key() |
Vector2i | region_key_a() |
Vector2i | region_key_b() |
Vector2i | other_region(region_key: Vector2i) |
Vector3 | normal_from(region_key: Vector2i) |
bool | is_aperture() |
StringName | property_key(field: StringName) |
String | get_portal_name() |
bool | set_portal_name(portal_name: String) |
void | clear_face_property(prop_name: StringName) |
Variant | get_face_property(prop_name: StringName, default = null) |
HammeriteFace | property_host(prop_name: StringName) |
Constants
PROPERTY_NAMESPACE
const PROPERTY_NAMESPACE = &"portal"
Namespace for authored values stored on a portal's host face, including its name.
NAME_FIELD
const NAME_FIELD = &"name"
Field under that namespace holding the portal's name.
Property descriptions
air_brush_a
var air_brush_a: int = 0
The lower of the two air brush ids (HammeriteBrush.id) this portal joins, so the pair is canonical regardless of author order.
air_brush_b
var air_brush_b: int = 0
The higher of the two air brush ids this portal joins; see air_brush_a.
region_a
var region_a: int = 0
Which room of air_brush_a's brush this portal opens into, as HammeriteCell.region. A brush split by a structural wall has several rooms in it, and an opening leads into one of them - so a consumer that wants "the room on this side" wants region_key_a().
region_b
var region_b: int = 0
Which room of air_brush_b's brush this portal opens into; see region_a.
opening_index
var opening_index: int = 0
Which of the openings joining this pair of brushes this is. Two rooms sharing a wall with two windows in it get two portals, 0 and 1, separately addressable. Assigned by the compiler in a canonical geometric order, so it stays with the same opening across an edit.
polygon
var polygon: PackedVector3Array = PackedVector3Array()
Convex polygon of the opening (world space, ordered loop). Empty on an overlap portal - see is_aperture().
plane
var plane: Plane = Plane(0, 0, 0, 0)
Plane the portal polygon lies on, its normal pointing out of side A's room and into side B's: a point in front of it is on side B. See normal_from(). A zero Plane() on an overlap portal.
host_face
var host_face: HammeriteFace = null
The authored HammeriteFace of side A's brush this aperture was punched from. Null on an overlap portal, which has no aperture and therefore no face (is_aperture()), and null when side A's wall came from a clip rather than an authored face.
host_face_b
var host_face_b: HammeriteFace = null
The authored face of side B's brush this aperture was punched from, as host_face. An aperture is punched from a PAIR of faces, one per room, and either may be the one that holds an authored value, so a read checks both (get_face_property()).
Method descriptions
make_key()
static func make_key(a: int, b: int, discriminator: int = 0) -> Vector3i
Canonical dictionary key for a portal joining air brushes a and b as opening discriminator of the pair: Vector3i(min(a, b), max(a, b), discriminator). Keep portal-key construction in this single place so every consumer (audio edges, per-portal overrides, lightmap adjacency, HammeriteMap's own portal index) agrees on the key shape.
key()
func key() -> Vector3i
This portal's canonical key, as make_key(). Stable across a recompile as long as the two brushes and the order of their openings are.
region_key_a()
func region_key_a() -> Vector2i
The region key (HammeriteCell.key()) of the room on side A.
region_key_b()
func region_key_b() -> Vector2i
The region key (HammeriteCell.key()) of the room on side B.
other_region()
func other_region(region_key: Vector2i) -> Vector2i
The room on the far side of this portal from region_key, or Vector2i.ZERO - which names no room, as no brush has id 0 - when the given room is not one of its two sides.
normal_from()
func normal_from(region_key: Vector2i) -> Vector3
The way through this opening from the room region_key: plane's normal from side A, reversed from side B. Zero when that room is not one of its sides, or on an overlap portal.
is_aperture()
func is_aperture() -> bool
True when this portal is a real APERTURE - a hole in a shared wall, with a polygon and a plane. The other kind is connectivity-only: two air brushes that interpenetrate have no aperture between them at all, because there is no wall there to make a hole in. They are one continuous open space, which is why the boundary-wall holing skips them and why they cannot be named.
property_key()
func property_key(field: StringName) -> StringName
Key for one of this portal's authored values on its host face: portal/<air brush a>/<air brush b>/<opening index>/<field> - the portal's own key(), then the field. The whole key, because one authored wall can host several openings. The opening index only counts the openings joining one PAIR of rooms: a hall and a corridor joined by two doorways resolve both portals to the same face, and so does a room wall with a doorway into one neighbour and a window into another, which are both opening 0 of their pair. Keyed by anything less, naming one would name the other. Consumers put their own values under their own field names; the key shape lives here so they agree on it. The cost is that the key names the two rooms by brush id, so an opening of a room that becomes a new brush - copied, or clipped in two - starts out unnamed. And the opening index falls out of where the openings are (see HammeriteSubtractiveCompiler.compile_portals()), so cutting a third opening between the same pair that sorts ahead of the others shifts the indices and the authored values with them. Moving geometry - by far the commoner edit - does neither.
get_portal_name()
func get_portal_name() -> String
The author's name for this opening, or "" if it was never named. A portal's name is the one identity every consumer shares. A window is a route for navigation and a hole for sound, and it is the same window - naming it twice, once per addon, would let the two disagree about which opening a door entity means. So the name lives HERE, on the portal (which is to say on the authored face beneath it), and the addons read it.
set_portal_name()
func set_portal_name(portal_name: String) -> bool
Name this opening, writing through to the face the aperture is punched from. Returns false when there is no face to write to - an overlap portal is not an aperture and cannot be named (is_aperture()). An empty name clears the property rather than storing a blank, keeping the face's storage sparse.
clear_face_property()
func clear_face_property(prop_name: StringName) -> void
Erase prop_name from BOTH host faces. Both, because an aperture is cut from a pair of them and either may be carrying the value: a name written to one side earlier must not survive as a ghost when the author clears it from the other.
get_face_property()
func get_face_property(prop_name: StringName, default = null) -> Variant
The authored value of prop_name taken from whichever host face carries one, or default. host_face wins a disagreement, but only because something has to.
property_host()
func property_host(prop_name: StringName) -> HammeriteFace
The face an authored value for prop_name should be WRITTEN to: whichever host face already carries one, else host_face, else host_face_b - a side whose wall was cut by structure rather than authored has none. Null when neither side has one, as on an overlap portal.