HammeriteMap
Core: the map. Inherits Resource
A level as authored: brushes, entities, layers and prefab instances, with the lighting baked for it and the world compiled from it.
The world is carved, not built: solid is the default and air brushes carve the rooms. Cells, portals and boundary walls are compiled from the brushes the first time something asks for them, cached, and compiled again after an edit; none of that is saved. Positions are in map space, the coordinates brushes are authored in. After loading a map, call rewire_objects() before anything else, then give it to a HammeriteMap3D to put it in a scene.
Properties
Methods
Signals
brush_added
signal brush_added(brush: HammeriteBrush)
Emitted by add_brush() once brush is in brushes. Not emitted for the brushes a load brings in (rewire_objects()).
brush_removed
signal brush_removed(brush: HammeriteBrush)
Emitted by remove_brush(), after brush has left brushes.
brush_face_attribute_changed
signal brush_face_attribute_changed(brush: HammeriteBrush)
Emitted when a face of brush changes texture, UVs or lightmap scale - the attributes that leave the compiled world's shape alone. For a tool preview it is also emitted when its shape changes.
brush_property_changed
signal brush_property_changed(brush: HammeriteBrush, prop_name: StringName, value)
Emitted when an extendable brush property changed, carrying the brush and the effective value. Properties are metadata rather than geometry, so this deliberately does NOT invalidate the worldrep: it exists so the consumers that DO care - the audio graph above all - can mark themselves stale without every property edit triggering a CSG recompile.
bake_warnings_changed
signal bake_warnings_changed()
Emitted when the set of bake warnings changes, so viewports redraw their markers.
entity_added
signal entity_added(entity: HammeriteEntity)
Emitted by add_entity() once entity is in entities.
entity_removed
signal entity_removed(entity: HammeriteEntity)
Emitted by remove_entity() - including when entity asked to be deleted, or was a brush entity whose last brush was removed.
prefab_instance_added
signal prefab_instance_added(instance: HammeritePrefabInstance)
Emitted by add_prefab_instance().
prefab_instance_removed
signal prefab_instance_removed(instance: HammeritePrefabInstance)
Emitted by remove_prefab_instance(). The instance's brushes and entities are still in the map.
layer_added
signal layer_added(layer: HammeriteLayer)
Emitted by add_layer().
layer_removed
signal layer_removed(layer: HammeriteLayer)
Emitted by remove_layer(), after what was on the layer has moved to the default one.
light_entity_lightmaps_changed
signal light_entity_lightmaps_changed(entity: HammeriteEntity)
Emitted during a bake each time a lightmap carrying the dynamic light entity is added (add_light_entity_lightmap()).
mesh_entity_lightmap_changed
signal mesh_entity_lightmap_changed(entity: HammeriteEntity)
Emitted when the prop_model entity is given a lightmap, or when the lighting on the map is swapped or restored and every prop's may have changed.
worldrep_lightmaps_baked
signal worldrep_lightmaps_baked()
Emitted once at the end of a structural (boundary-face) bake so views rebuild the cell mesh to show the freshly-assigned lightmaps. Distinct from light_entity_lightmaps_changed, which only fires for lightmaps carrying DYNAMIC lights - a static-only bake assigns none and so never fired it, leaving the cell mesh dark. Does NOT invalidate the graph.
light_probes_changed
signal light_probes_changed()
Emitted when probe_set is replaced: probes placed afresh (generate_light_probes()), or another variant's lighting, or a snapshot's, put on the map.
light_probes_baked
signal light_probes_baked()
Emitted when a bake has finished with the probes: each holds this bake's light, and each of its switchable slots the lamp it answers to. Anything made FROM the probes - a grid of the light in the air for a game's own effects - is made now, not on worldrep_lightmaps_baked, which comes before the probes are finished.
custom_data_changed
signal custom_data_changed(key: String)
Emitted when custom_data under key may have changed: by set_custom_data(), and for every key either side held when another variant's custom_data is swapped in.
worldrep_changed
signal worldrep_changed()
Emitted when the compiled worldrep is invalidated (structural brush add / remove / geometry / tier / ownership change), so views can rebuild their worldrep display from get_worldrep_solids() without wiring per-brush signals themselves.
slice_changed
signal slice_changed()
Emitted when an editor depth-slice plane moves (2D floorplan views), so the ortho previews redraw. Transient view state - the slice planes are not serialized.
Experimental: The editor's view state, not part of the supported API.
variant_changed
signal variant_changed()
What exists in the map changed without anything being edited: another variant was made active, or a layer became or stopped being one, or moved in or out of one. See is_layer_present().
sky_changed
signal sky_changed()
The sky in effect may be another one: see get_sky().
Enumerations
enum WarningKind
| Value | Name | Description |
|---|---|---|
0 | SLIVER_GAP | Two air brushes nearly touch - closer than SLIVER_GAP_MAX - but do not connect. |
1 | AMBIGUOUS_OVERLAP | Two overlapping air cells of equal volume, so which one owns a point falls to their ids. |
2 | VOID_WINDOW | A cell wall that is see-through with nothing but rock behind it (HammeriteBoundaryWall.looks_into_void()). About one wall rather than a pair of rooms, so it names a GraphWarning.face and one brush. |
enum BakeWarningKind
| Value | Name | Description |
|---|---|---|
0 | FACE_TOO_LARGE | The face's lightmap footprint exceeds a whole atlas, so it was skipped and has NO lightmap: it renders unlit. Lower the face's lightmap_scale (bigger number = coarser texels) or split it. |
1 | LIGHT_OVERFLOW | More than four distinct light groups reach the face. A face has four slots, so the extras were dropped. Group lights that belong to one effect, or move a light. |
2 | WASTEFUL_FACE | The face holds a lot of lightmap texels and almost none of them were reached by any light, so that atlas space is spent on black. Raise its lightmap_scale, or light the surface. |
enum BakeState
| Value | Name | Description |
|---|---|---|
0 | NOT_BAKED | Never baked: it renders unlit. |
1 | BAKED | Baked, and nothing the bake read has changed since. |
2 | STALE | Baked, and something the bake read has changed since. |
Constants
BASE_ENTITY_PROPS
const BASE_ENTITY_PROPS = {&"_aabb": AABB(-4, -4, -4, 8, 8, 8), &"name": "", &"origin": Vector3(0, 0, 0)}
The properties every entity type starts from, before its registration is merged over them (HammeriteMapEditor.register_point_entity_type()): origin, name and _aabb, the box the editor picks it by. Read-only; duplicate it before changing it.
NATIVE_CELL_INDEX
const NATIVE_CELL_INDEX = &"HammeriteNativeCellIndex"
The class name of the native kernel native_cells uses.
Experimental: A switch for the native kernel and its tests.
BOUNDARY_NUDGE
const BOUNDARY_NUDGE = 0.05
How far segment_in_air() steps off a cell boundary before deciding a point is in solid. Sized to clear the inside-test's epsilon, and nothing more.
CELL_HASH_CELL_SIZE
const CELL_HASH_CELL_SIZE = 128.0
Edge length of the uniform grid point_in_cell() buckets cells into. Smaller than a room, because a lookup scans every cell in its bucket: at 256 the reference level's lookups scanned 108 cells to find the 2 whose bounds held the point, and navigation makes hundreds of thousands of them. At 128 they scan 37, for an index less than twice the size; finer again, the index - rebuilt after every edit - grows faster than the scans shrink.
CELL_HASH_MAX_BUCKETS
const CELL_HASH_MAX_BUCKETS = 512
A cell spanning more buckets than this is kept in a separate always-tested list instead of being written into every one of them. Without it a single sky-sized outdoor cell would fill the grid and the index would cost more than the scan it replaces. Four times the old 128, with buckets an eighth the volume: a cell that fitted then still fits.
MAX_SEGMENT_CELL_STEPS
const MAX_SEGMENT_CELL_STEPS = 256
Backstop on segment_in_air()'s cell walk. Far above any real room count along one line.
PROBE_GATHER_COUNT
const PROBE_GATHER_COUNT = 8
How many probes light a point, and how far out to look for them - in probe cells, and see query_light_probes() for why it is a bound rather than a working distance.
LIGHT_TYPES
const LIGHT_TYPES = Array[StringName]([&"light_point", &"light_point_dynamic", &"light_spot", &"light_spot_dynamic", &"light_directional", &"light_directional_dynamic"])
Core's light types, static and dynamic. These, and any type derived from one (HammeriteEntityTypeRegistry.is_light()), are what get_lights() returns and what a bake reads as a light.
PROBE_GATHER_RADIUS_CELLS
const PROBE_GATHER_RADIUS_CELLS = 8.0
How far out query_light_probes() looks, in multiples of the probe set's HammeriteLightProbeSet.cell_size; see PROBE_GATHER_COUNT.
PARTIAL_BAKE_FINGERPRINT
const PARTIAL_BAKE_FINGERPRINT = "partial"
The HammeriteVariantBake.fingerprint of lighting a quick bake added to nothing: it matches no world, so the bake reads as out of date.
NATIVE_PICK_INDEX
const NATIVE_PICK_INDEX = &"HammeriteNativePickIndex"
The class name of the native kernel that answers native_picking's queries.
Experimental: A switch for the native kernel and its tests.
NATIVE_OCCLUDERS
const NATIVE_OCCLUDERS = &"HammeriteNativeOccluders"
The class name of the native kernel native_occluders uses.
Experimental: A switch for the native kernel and its tests.
WALL_OCCLUDER_THICKNESS
const WALL_OCCLUDER_THICKNESS = 1.0
Thickness (Hammerite units) of the synthesised wall-shell occluder prisms built by build_occluder_bvh(). Tied to SLIVER_GAP_MAX: a prism cannot punch through a solid divider thinner than the sliver-gap validator already flags as an authoring mistake. Only a robustness / don't-intrude-into-a-neighbour knob - any positive thickness blocks a ray crossing the wall polygon, so it does not affect *whether* light is occluded.
VOID_WALLS_KEY
const VOID_WALLS_KEY = &"hammerite/void_walls"
The key get_void_walls() keeps its answer under in derived.
SLIVER_GAP_MAX
const SLIVER_GAP_MAX = 1.0
Max gap (Hammerite units; 1.0 ~ 1 inch) below which two non-connecting air cells are flagged as a likely-unintended sliver gap by validate_graph(). Above this a gap reads as a real wall and is left alone.
Property descriptions
default_collision_layer
var default_collision_layer: int = 4294967295
The physics layers the world's collider and the detail brushes' bodies are put on when HammeriteMap3D builds them. Read at build time; changing it later moves nothing already built.
default_collision_mask
var default_collision_mask: int = 4294967295
The physics mask given to the world's collider and the detail brushes' bodies; see default_collision_layer.
default_brush_entity_type
var default_brush_entity_type: StringName = &"group"
Default brush entity type used for newly-created brush entities (Ctrl+T in the editor). Points at Hammerite's own generic, always-working brush entity type (see HammeriteMapEditorCore.register_default_entity_types()). Games can freely override this to point at their own registered type instead.
brushes
var brushes: Dictionary[int, HammeriteBrush] = {}
Every authored brush, by HammeriteBrush.id, in the order the compiler carves them. Read it freely, but change it only through add_brush() and remove_brush(), which keep the compiled world and the signals in step; writing to it directly does neither.
entities
var entities: Dictionary[int, HammeriteEntity] = {}
Every entity, by HammeriteEntity.id. Change it through add_entity() and remove_entity().
prefab_instances
var prefab_instances: Dictionary[int, HammeritePrefabInstance] = {}
Every placed prefab, by HammeritePrefabInstance.instance_id. Change it through add_prefab_instance() and remove_prefab_instance().
properties
var properties: Dictionary = {}
Free key/value storage saved with the map. Hammerite itself reads nothing from it; for data an extension works out from the world, use custom_data.
layers
var layers: HammeriteLayer[] = []
Every layer; nesting is by HammeriteLayer.parent_id, not by order. Layer 0, "Default", always exists. Change it through add_layer() and remove_layer().
default_variant_id
var default_variant_id: int = 0
The variant layer every map has, and the one in effect until another is asked for. See HammeriteLayer.variant.
variant_bakes
var variant_bakes: Dictionary[int, HammeriteVariantBake] = {}
Variant layer id -> the lighting last baked in it. The one whose lighting is on the map now is applied_variant_id.
Experimental: What a bake stored, written by the lightmapper; read it through the getters.
variant_custom_data
var variant_custom_data: Dictionary[int, Dictionary] = {}
Variant layer id -> its custom_data, for every variant but applied_variant_id.
Experimental: What a bake stored, written by the lightmapper; read it through the getters.
applied_variant_id
var applied_variant_id: int = 0
The variant whose lighting and custom_data are on the map. 0 for the default: a map made before it had variants was made in what is now its default.
Experimental: What a bake stored, written by the lightmapper; read it through the getters.
light_lightmaps
var light_lightmaps: Dictionary[int, Array] = {}
Light entity id -> the HammeriteBakedLightmaps that light's dynamic contribution was baked into. Written by the bake; read it through get_lightmaps_for_light_entity().
Experimental: What a bake stored, written by the lightmapper; read it through the getters.
mesh_entity_lightmaps
var mesh_entity_lightmaps: Dictionary[int, HammeriteBakedLightmap] = {}
prop_model entity id -> its baked lightmap. Written by the bake; read it through get_mesh_entity_lightmap().
Experimental: What a bake stored, written by the lightmapper; read it through the getters.
baked_lights
var baked_lights: Dictionary[int, HammeriteBakedLight] = {}
Shared per-light-entity HammeriteBakedLight registry, keyed by the light entity's id. This holds the single canonical HammeriteBakedLight instance for each baked light; every HammeriteBakedLightmap slot that light appears in references the same instance, so fetching it via get_baked_light() and mutating it (e.g. set_modulation) ripples to every atlas through the existing signal chain. Populated at bake time by the two lightmap sinks and re-derived in rewire_objects(). Slots with entity id 0 are anonymous padding and are never registered here.
Experimental: What a bake stored, written by the lightmapper; read it through the getters.
baked_lights_by_group
var baked_lights_by_group: Dictionary[StringName, HammeriteBakedLight] = {}
Canonical HammeriteBakedLight per light group, for get_baked_light_for_group(). Derived from baked_lights on load and on bake, never serialized separately - a group's HammeriteBakedLight is the same instance as its representative entity's.
Experimental: What a bake stored, written by the lightmapper; read it through the getters.
probe_set
var probe_set: HammeriteLightProbeSet = <unknown>
The baked light probes of the active variant. Replaced, not emptied, by a new bake or a variant switch, so do not hold on to it across either; light_probes_changed says when.
mesh_uv2s
var mesh_uv2s: Dictionary[int, PackedVector2Array] = {}
UV2 coordinates for mesh entities (prop_model), keyed by entity ID. Used by the lightmapper to store unwrapped UVs for mesh props.
Experimental: What a bake stored, written by the lightmapper; read it through the getters.
custom_data
var custom_data: Dictionary = {}
Custom data storage for extensions (e.g., acoustic system) Usage: custom_data["audio"] = AudioData resource The active variant's own: what an extension works out from the world differs between variants as the world does, so making another variant active swaps it for that variant's.
references
var references: Dictionary = {}
Who provided each thing this map names from outside itself when it was saved - kind -> name -> {"provider", "version"} (HammeriteReferences.record()). Not derived data: what provided a texture then cannot be worked out again later, and that is the whole of what it is for.
native_cells
var native_cells: bool = <unknown>
Whether cells are indexed by the native kernel (#116), which finds exactly the cells the GDScript index does, in the same order. The suites turn it off to check one against the other.
Experimental: A switch for the native kernel and its tests.
worldrep_from_cache
var worldrep_from_cache: bool = false
Whether the worldrep in hand came off disk rather than out of the compiler. Goes false at the first edit, because from then on it is partly compiled. Read it to say where a level's geometry came from - a cache that has quietly stopped matching shows up as a slow load and nothing else, so being able to ask is most of the diagnosis.
compile_progress
var compile_progress: HammeriteCompileProgress
Where a compile reports how far it has got, and learns it has been cancelled. Null, the usual case, costs the compile nothing. Set by HammeriteWorldrepCompile; derived, not serialized.
program_version
var program_version: int = 0
Raised whenever which brushes the compiled program holds, or their order, may have changed: a brush added or removed, one of the fields HammeriteSubtractiveCompiler orders and admits brushes by, or which layers are present. The compiler orders the program again only when this has moved, rather than on every edit - which was most of the fixed cost of one on the reference level. Anything that changes what the program is read from has to raise it; HammeriteSubtractiveCompiler.verify_program catches what does not.
Experimental: The compiler's cache key, raised by whatever changes the brush program.
include_previews_in_worldrep
var include_previews_in_worldrep: bool = false
Let tool previews take part in the compiled world (WYSIWYG previews). A tool preview is an is_temporary brush, normally skipped by HammeriteSubtractiveCompiler so it cannot carve before it is committed. With this on it DOES carve, which is the whole point: an air brush being dragged shows the room it is about to cut, and a solid shows as real textured geometry rather than a wireframe box. The cost is a worldrep recompile per edit of the preview, so the editor exposes it as a setting (HammeriteMapEditorSettings.wysiwyg_previews) to turn off on maps where that bites. Not serialized: it describes how the editor is displaying the map right now, not the map.
Experimental: The editor's view state, not part of the supported API.
brush_count
var brush_count: int = 0
Running counter for generating brush ids
Experimental: The map's id counters, advanced by the gen_*_id functions.
entity_count
var entity_count: int = 0
Running counter for generating entity ids
Experimental: The map's id counters, advanced by the gen_*_id functions.
instance_count
var instance_count: int = 0
Running counter for generating prefab instance ids
group_count
var group_count: int = 0
Running counter for generating group ids
Experimental: The map's id counters, advanced by the gen_*_id functions.
layer_count
var layer_count: int = 0
Running counter for generating layer ids
Experimental: The map's id counters, advanced by the gen_*_id functions.
native_picking
var native_picking: bool = <unknown>
Whether picking asks the native kernel (#116), which answers is_wall_drawn_at() and aperture_at() exactly as they do, from only the walls and portals near the point. Asked of every wall in the level, one pick took a third of a second on the reference level - and the hover highlight picks whenever the mouse moves. The suites turn it off to check one against the other.
Experimental: A switch for the native kernel and its tests.
native_occluders
var native_occluders: bool = <unknown>
Whether build_occluder_bvh() has the native kernel make its wall prisms and tree. Settable, so a suite can build both ways.
Experimental: A switch for the native kernel and its tests.
skybox
var skybox: HammeriteSkybox = null
What is outside this map: the scale model and the sky over it, drawn wherever a sky face is. One model per map, deliberately - which part of a city a map is in is a matter of moving the virtual camera inside a shared model, not of building a second model. The default variant's sky, and every other variant's unless it has its own (variant_skies). Read get_sky() for the one in effect.
derived
var derived: Dictionary[StringName, Variant] = {}
What a stage hooked into HammeriteWorldrepCompile derived from this map's compiled world, by the key that stage chose - navigation built on the compile's worker, so the thread that takes the map only has to attach it. Not saved, and dropped the moment the worldrep goes stale, so nothing here can describe a level that has since been edited.
variant_skies
var variant_skies: Dictionary[int, HammeriteSkybox] = {}
Variant layer id -> that variant's own sky: the same streets at dusk, or under rain. Null is a variant with no sky at all. A variant not listed has the sky of the one it sits inside, and in the end the default's.
baked_surfaces
var baked_surfaces: Dictionary = {}
The bake's result, kept where a recompile cannot lose it: which lightmap each wall of the world ended up in, and where in it - surface key (surface_key()) -> [lightmap, uv2, lightmap_scale]. Why this has to exist. A bake writes onto BOUNDARY faces, and a boundary face is derived. For an air brush nothing carves, the fragment IS the authored brush, so those faces are the authored ones - exported, saved, and back after a load with their lighting on them. Carve the same room with a solid, put a pillar in a hall, and the fragments are clipped COPIES: the bake writes onto objects no save can see, and the room comes back black while every uncarved room beside it is fine. That is not a rare case. It is every room with anything standing in it. Keyed by where a wall IS - its plane and the middle of its polygon - because that is what a recompile of unchanged geometry reproduces exactly, and what an edit changes. A wall that moved finds no entry and stays unlit, which is the truth: its bake is stale.
Experimental: What a bake stored, written by the lightmapper; read it through the getters.
dark_surfaces
var dark_surfaces: Dictionary = {}
Where the last bakes found a face and left it dark: surface key (surface_key()) -> true. A dark face has no lighting to keep, so without this it looks to a quick bake just like one an edit has made since.
Experimental: What a bake stored, written by the lightmapper; read it through the getters.
Method descriptions
rewire_objects()
func rewire_objects() -> void
Put a freshly loaded map in working order: connect every brush, entity, layer and prefab instance to it, put the brushes in carving order, recount the id counters, rebuild the baked-light registry and make sure the default variant exists. Call it once after loading (HammeriteDataFile.read() or ResourceLoader.load()) and before anything else: until then its brushes and entities are not connected to it, so editing them recompiles nothing. Brushes with invalid geometry are removed, brush entities with no brushes dropped, and a layer inside itself moved to the top level. Safe to call again.
add_brush()
func add_brush(brush: HammeriteBrush) -> void
Put brush in the map and mark where it stands for recompiling; emits brush_added. brush needs an id first (gen_brush_id()). Adding one already in the map does nothing; another brush under its id is an error, and is not added. A brush that names an owning entity (HammeriteBrush.brush_entity_id) joins that entity, now if it is in the map and when it is added otherwise.
remove_brush()
func remove_brush(brush: HammeriteBrush) -> void
Take brush out of the map and mark where it stood for recompiling; emits brush_removed; does nothing for a brush the map does not hold. If it belonged to a brush entity it is unassigned, and an entity left with no brushes deletes itself. Also what HammeriteBrush.delete_requested calls.
add_entity()
func add_entity(entity: HammeriteEntity) -> void
Put entity in the map; emits entity_added. It needs an id first (gen_entity_id()). Adding one already in the map does nothing; another entity under its id is an error, and is not added. A brush entity takes the brushes already in the map that name it (HammeriteBrush.brush_entity_id), and gains others through HammeriteBrushEntity.assign_brush().
create_entity()
func create_entity(typename: StringName, origin: Vector3 = Vector3(0, 0, 0), layer_id: int = 0) -> HammeriteEntity
A new entity of typename, standing at origin on layer layer_id, with every property its type declares at its default, an id of this map's, and in the map: a game spawning something at run time needs nothing else. A brush entity has no origin; give it its brushes with assign_brush_to_entity(). See HammeriteEntity.make() for one not yet in a map.
remove_entity()
func remove_entity(entity: HammeriteEntity) -> void
Take entity out of the map; emits entity_removed. A brush entity's brushes stay in the map as ordinary brushes, owned by nothing. Also what HammeriteEntity.delete_requested calls.
add_layer()
func add_layer(layer: HammeriteLayer) -> void
Append layer to layers and start following its visibility, parent and variant flag. It needs a non-zero id first (gen_layer() makes one with an id). A variant, or a layer nested in another, recompiles the world.
capture_layer()
func capture_layer(layer: HammeriteLayer) -> Dictionary
Everything remove_layer() would change, for restore_layer() to put back: where the layer is in the list and the tree, what is on it, the layers inside it, and what it keeps as a variant.
restore_layer()
func restore_layer(layer: HammeriteLayer, captured: Dictionary) -> void
Undo remove_layer(), from what capture_layer() took before it.
remove_layer()
func remove_layer(layer: HammeriteLayer) -> void
Take layer out of the map, moving what was on it to the default layer; emits layer_removed. Layer 0 and the default variant cannot be removed - every map has them - and asking is an error.
add_light_entity_lightmap()
func add_light_entity_lightmap(entity: HammeriteEntity, lightmap: HammeriteBakedLightmap) -> void
Record that lightmap carries entity's dynamic light; emits light_entity_lightmaps_changed. Called by the lightmapper during a bake.
Experimental: The lightmapper's own: a game calling it can corrupt a bake.
notify_worldrep_lightmaps_baked()
func notify_worldrep_lightmaps_baked() -> void
Notify views that a structural bake finished writing boundary-face lightmaps (see signal worldrep_lightmaps_baked). Called by the lightmapper at the end of write_lightmaps, after every face has its lightmap assigned, so the debounced cell-mesh rebuild picks up the fresh lighting.
Experimental: The lightmapper's own: a game calling it can corrupt a bake.
notify_light_probes_baked()
func notify_light_probes_baked() -> void
Notify that a bake has finished with the probes (see signal light_probes_baked). Called by the lightmapper at the end of write_probes.
Experimental: The lightmapper's own: a game calling it can corrupt a bake.
set_mesh_entity_lightmap()
func set_mesh_entity_lightmap(entity: HammeriteEntity, lightmap: HammeriteBakedLightmap) -> void
Give the prop_model entity its baked lightmap; emits mesh_entity_lightmap_changed. Called by the lightmapper during a bake.
Experimental: The lightmapper's own: a game calling it can corrupt a bake.
get_mesh_entity_lightmap()
func get_mesh_entity_lightmap(entity_id: int) -> HammeriteBakedLightmap
The baked lightmap of the prop_model entity entity_id, or null if it has none.
get_baked_light()
func get_baked_light(entity_id: int) -> HammeriteBakedLight
Return the single shared HammeriteBakedLight for a light entity id, or null if that light has no baked data. Mutating the returned instance (e.g. set_modulation) updates every atlas it contributes to through the existing signal chain.
get_baked_light_for_entity()
func get_baked_light_for_entity(entity: HammeriteEntity) -> HammeriteBakedLight
The HammeriteBakedLight carrying entity's contribution: its own, or its GROUP's when it shares a slot with other lights. Note grouped lights cannot be modulated individually, by construction: the slot holds their sum, so there is no separate contribution to turn down. A group member therefore resolves to the shared handle, and driving it drives the whole effect - which is the point of grouping.
get_baked_light_for_group()
func get_baked_light_for_group(group: StringName) -> HammeriteBakedLight
The shared HammeriteBakedLight for a light GROUP, or null. Lights sharing a non-empty HammeriteLightmapLight.group bake into one slot and one HammeriteBakedLight, so this is the handle that drives a whole multi-entity effect - a lightning flash made of four directional entities is map.get_baked_light_for_group(&"lightning").set_modulation(flash), one call.
get_brushes_in_group()
func get_brushes_in_group(group_id: int) -> HammeriteBrush[]
The brushes whose HammeriteBrush.group_id is group_id, as a new array. Group 0 is every ungrouped brush. A scan of every brush.
get_entities_in_group()
func get_entities_in_group(group_id: int) -> HammeriteEntity[]
The entities whose HammeriteEntity.group_id is group_id, as a new array. Group 0 is every ungrouped entity.
get_brushes_in_layer()
func get_brushes_in_layer(layer_id: int) -> HammeriteBrush[]
The brushes directly on layer layer_id, not on the layers inside it, as a new array.
get_entities_in_layer()
func get_entities_in_layer(layer_id: int) -> HammeriteEntity[]
The entities directly on layer layer_id, not on the layers inside it, as a new array.
get_instances_in_layer()
func get_instances_in_layer(layer_id: int) -> HammeritePrefabInstance[]
The prefab instances directly on layer layer_id, as a new array.
get_layer_by_id()
func get_layer_by_id(layer_id: int) -> HammeriteLayer
The layer with id layer_id, or null if there is none. A scan of layers.
get_child_layers()
func get_child_layers(layer_id: int) -> HammeriteLayer[]
The layers directly inside layer_id, in layer order. Empty for 0: top-level layers are not children of the default layer.
get_descendant_layers()
func get_descendant_layers(layer_id: int) -> HammeriteLayer[]
Get all descendant layers of a given layer (recursive)
apply_layer_ghosting()
func apply_layer_ghosting(layer: HammeriteLayer) -> void
Put the ghosting of layer and everything under it onto their brushwork (#69). Ghosting cascades the way locking does and the opposite way to visibility: a layer inside a ghosted one is ghosted whatever it says about itself, so a parent being ghosted has to reach the brushwork of every layer below it. Without this a parent ghosted itself and left its children solid, which reads as the ghosting not having worked. Each layer is re-read from its OWN state rather than handed the parent's answer: a child ghosted in its own right stays ghosted when the parent stops being, which it would not if "no longer ghosted" were simply written down the tree.
is_layer_within()
func is_layer_within(layer_id: int, ancestor_id: int) -> bool
Whether ancestor_id is layer_id or a layer it sits inside. What a reparent has to refuse, since a layer inside itself would have no top.
get_variants()
func get_variants() -> HammeriteLayer[]
Every variant layer, in layer order.
find_variant()
func find_variant(variant_name: String) -> HammeriteLayer
The variant layer named variant_name, or null. How a game asks for one.
get_default_variant()
func get_default_variant() -> HammeriteLayer
The variant every map has (default_variant_id), or null on a map not yet rewired (rewire_objects()).
get_active_variant()
func get_active_variant() -> HammeriteLayer
The variant in effect. The default one unless another has been asked for.
set_active_variant()
func set_active_variant(layer: HammeriteLayer, take_up_cache: bool = true) -> void
Make layer the variant in effect; null for the default one. What is on other variants stops existing - it does not carve, is not baked and is not built - until its own is made active again. take_up_cache false leaves the variant's saved worldrep for HammeriteWorldrepCompile to read on its worker, rather than reading it here.
is_active_variant_baked()
func is_active_variant_baked() -> bool
Whether the active variant has been baked.
get_bake_state()
func get_bake_state(variant: HammeriteLayer) -> int
Whether variant's lighting has been baked, and whether it still describes that variant's world. Worked out without making it active, so a list of variants can say it of every one.
lighting_fingerprint()
func lighting_fingerprint() -> String
Digest of everything a bake of the active variant reads: the brushes present - shape, textures, how their faces map them, their properties - and the lights, props and decals, and the sky. Two worlds with the same one bake the same.
apply_active_variant()
func apply_active_variant() -> void
Put the active variant's lighting and custom_data on the map, if they are not what is there already. One never baked is unlit rather than lit as some other variant was. A host that loads a map to play it calls this through HammeriteMap3D, since the map was saved with whichever variant its author was looking at.
store_variant_bake()
func store_variant_bake(key: int = -1) -> void
Keep what was just baked as key's lighting - the active variant's unless another is named. Called when a bake has finished (see notify_light_probes_baked()).
Experimental: The lightmapper's own: a game calling it can corrupt a bake.
store_partial_bake()
func store_partial_bake() -> void
Keep the lighting HammeriteLightmapper.quick_bake() added to as the active variant's. It lit only the faces that had none, by direct light, so the world is no better described than by the bake it added to: that bake's fingerprint is kept, and the state stays as out of date as it was.
Experimental: The lightmapper's own: a game calling it can corrupt a bake.
snapshot_lighting()
func snapshot_lighting() -> HammeriteMap.LightingSnapshot
Take down the lighting on the map now, so a bake that is abandoned can put it back.
restore_lighting()
func restore_lighting(snapshot: HammeriteMap.LightingSnapshot) -> void
Put back the lighting snapshot took down, and tell the views - whatever a bake has written since. Only what differs is touched, so faces the bake had not reached are not remeshed.
variant_of()
func variant_of(layer_id: int) -> HammeriteLayer
The variant layer_id belongs to: the nearest variant layer it is or sits inside, or null for a layer every variant shares.
is_layer_present()
func is_layer_present(layer_id: int) -> bool
Whether what is on layer_id exists in the active variant: every variant layer it is or sits inside has to be the active one or one the active one sits inside. A variant inside another refines it, so making the inner one active brings in the outer one's contents as well.
is_layer_present_in()
func is_layer_present_in(layer_id: int, variant_id: int) -> bool
Whether what is on layer_id exists in the variant variant_id (0 the default), whichever is active: is_layer_present() asked of another world.
is_brush_present_in()
func is_brush_present_in(brush: HammeriteBrush, variant_id: int) -> bool
is_brush_present() asked of the variant variant_id (0 the default), whichever is active.
is_brush_present()
func is_brush_present(brush: HammeriteBrush) -> bool
Whether brush exists in the active variant: its layer is present (is_layer_present()), and so is the entity that owns it, if one does - a door in another variant takes its leaf along.
is_entity_present()
func is_entity_present(entity: HammeriteEntity) -> bool
Whether entity exists in the active variant: whether its layer is present (is_layer_present()).
get_brushes_for_entity()
func get_brushes_for_entity(entity_id: int) -> HammeriteBrush[]
The brushes owned by the brush entity entity_id, as a new array; empty for any other entity, or for an id the map does not have.
create_box_brush()
func create_box_brush(box: AABB, texture_name: StringName = &"", volume: int = 0, layer_id: int = 0) -> HammeriteBrush
A box filling box (HammeriteBrush.from_aabb()), every face textured texture_name, adding solid or carving air as volume says, on layer layer_id, with a fresh id and in the map: an air box from A to B is one call.
gen_cube_brush()
func gen_cube_brush(origin: Vector3, texture: Texture2D) -> HammeriteBrush
A new brush with a fresh id: a cube of zero size at origin, every face wearing texture, for a tool to drag out to its real size. Not added to the map, and not valid geometry until it is given a size; a box of a size is create_box_brush().
Experimental: The drag tool's seed, not part of the supported API.
gen_layer()
func gen_layer() -> HammeriteLayer
A new layer with a fresh id, named "Layer <id>". Not added to the map: pass it to add_layer().
gen_brush_id()
func gen_brush_id() -> int
A brush id never used in this map before. Advances brush_count, which is saved with the map.
gen_entity_id()
func gen_entity_id() -> int
An entity id never used in this map before. Advances entity_count, which is saved with the map.
gen_group_id()
func gen_group_id() -> int
Generate and return a new group ID.
gen_layer_id()
func gen_layer_id() -> int
Generate and return a new layer ID.
assign_brush_to_entity()
func assign_brush_to_entity(brush: HammeriteBrush, entity: HammeriteBrushEntity) -> void
Make entity the owner of brush, which takes the brush out of the structural world and into the entity's. entity needs an id; the brush is not taken from any entity that owned it before. HammeriteBrushEntity.assign_brush() is the same call from the entity's side.
unassign_brush_from_entity()
func unassign_brush_from_entity(brush: HammeriteBrush, entity: HammeriteBrushEntity) -> void
Undo assign_brush_to_entity(): brush belongs to no entity again. An entity left with no brushes deletes itself.
clear_lightmap_data()
func clear_lightmap_data() -> void
Throw away the lighting of the last bake - every face's lightmap, the lights' and props' lightmaps, and the bake warnings - and remesh what was lit. Light probes and baked_surfaces are kept. Called by a bake just before it writes its new lightmaps.
Experimental: The lightmapper's own: a game calling it can corrupt a bake.
get_lights()
func get_lights() -> HammeriteEntity[]
Every light entity (HammeriteEntityTypeRegistry.is_light()) present in the active variant, as a new array.
get_lightmaps_for_light_entity()
func get_lightmaps_for_light_entity(entity_id: int) -> HammeriteBakedLightmap[]
The lightmaps the dynamic light entity_id was baked into, as a new array; empty for a static light or one never baked.
get_aabb()
func get_aabb() -> AABB
The box around every authored brush's corners, in map space, whatever its layer or variant; an empty AABB for a map with no brushes.
convex_hull_from_faces()
func convex_hull_from_faces(input_faces: HammeriteFace[], texture: Texture2D) -> HammeriteBrush
A new brush that is the convex hull of input_faces' corners, every face wearing texture, or null (with an error) when they span no volume. The brush has no id and is not added to the map.
convex_hull()
func convex_hull(input_brushes: HammeriteBrush[], texture: Texture2D) -> HammeriteBrush
A new brush that is the convex hull of input_brushes. A hull face lying exactly on a face of one of them keeps that face's texture; the rest wear texture. Null (with an error) when they span no volume. The brush has no id, no tier and is not added to the map - see merge_brushes().
generate_light_probes()
func generate_light_probes(cell_size: float = 64.0, surface_offset: float = 1.0) -> void
Replace probe_set with probes placed afresh, unlit: surface probes surface_offset off every boundary wall, and volume probes on a cell_size lattice through the air, kept clear of the walls. Compiles the world if it has to; emits light_probes_changed. A bake calls this first; the probes carry no light until the bake has run.
query_light_probes()
func query_light_probes(pos: Vector3) -> HammeriteLightProbe[]
The probes that light pos: the nearest few that can actually be seen from there. The visibility test is segment_in_air(), and it is worth saying why it cannot be whether the line crosses an authored face, which is what this used to ask. In a carved world that test is wrong in BOTH directions, and this is the shape the wrongness took: - Air brushes are brushes, so a line across an open room crosses the room's own faces and reads as blocked. Probes were thrown away for being on the other side of nothing at all, and whatever they lit went black. - A structural solid is one authored block with the rooms carved out of it, so a line straight through rock crosses none of its faces and reads as clear. Probes in the lit hall next door were kept, and things in a dark cellar were lit by them. Both at once, on the same rope: its top segments unlit and its bottom ones bright in the dark. That test is also what the gather radius costs - a cell walk per probe gathered - so the reach below is a bound on the walk rather than an invitation to it. It only ever binds where the bake is sparse: HammeriteLightProbeSet.query() stops as soon as the probes it has cannot be beaten, which on a normal bake is the neighbouring cells and no further, whatever this says. So it is set to cover a ROOM, because a sparse room's own probes are what a point in it must still be able to reach - not to the shortest reach a dense bake would be happy with.
light_level_at()
func light_level_at(point: Vector3) -> float
How much light reaches point right now: the red, green and blue the probes that can see it carry, summed - direct and static light linear in the lights' energies, and the switchable bounce compressed below 1 a slot - plus what a carried light puts on it. Not normalized to anything: a game reads it at a point of its own whose brightness it knows, and puts its curve there. Hammerite's half of a stealth system, and deliberately only that half: this measures, and what counts as "dark enough to hide in" is the game's to decide (a curve over this, a reference brightness, what crouching is worth). Measuring belongs here because it is a question about the world - which probes can see this point, and what is switched on - and because a light level is just as useful to navigation, to a tool, or to anything else that wants to know where the dark is. Both halves of what a probe carries: the four switchable slots at their current modulation, and the static light that has no switch. Weighted by inverse square like everything else that reads probes, and faded towards the edge of the gathered set so that walking about does not make the number jump as which probes are nearest changes. And whatever a HammeriteCarriedLight3D with a clear line to the point puts on it, so a guard's torch lights them for whoever is watching. Returns 0.0 where no probe can see the point - which is a place with no light in it, and also a place the bake never reached; those are the same answer to anything standing there in the dark.
update_probe_lit_uniforms()
func update_probe_lit_uniforms(mat: ShaderMaterial, probes: HammeriteLightProbe[]) -> void
Write the first eight of probes - normally query_light_probes()' answer - and the lights in their slots into mat's probe uniforms (probe_count, probe_positions and the rest), for a shader that includes probe_sampling.gdshaderinc. Does nothing without a shader. Call it again when the probes or a light's modulation change; nothing here follows them.
offset_into_air()
func offset_into_air(position: Vector3, normal: Vector3, owner: HammeriteBrush, distance: float) -> Vector3
Offset a point distance off a surface, INTO the air. Not simply +normal any more. A face normal points out of its own brush: for a SOLID that is into the open room, but for an AIR brush - and a room IS an air brush now - it points out of the room and into the surrounding rock. owner is the brush the surface belongs to, when known. The convention is only the first guess; the cells decide. Whichever side actually contains air wins, which covers what the convention cannot know - a detail brush flush against a wall, a face shared by two cells. When neither side is air the point is returned offset by the convention, since there is nothing better to say.
generate_surface_probes()
func generate_surface_probes(spacing: float, offset: float) -> void
Generate surface probes on the compiled cell walls. Surface probes sample the light arriving at a wall so it can be redistributed as indirect light. They therefore have to sit in the ROOM, a little off the wall: a probe buried in solid sees no lit surface and contributes nothing, and one that lands in the solid between two rooms can gather in one and bleed into the other. The source geometry is get_boundary_walls() - the compiled cell walls, with portal and interpenetration apertures already punched out. That is what makes the placement correct rather than approximately correct. The previous implementation walked the AUTHORED brush faces, so an air face that a solid had carved or split still produced probes over its whole original extent, including the parts now inside rock: on an authored test map 142 of 368 surface probes (39%) were buried in solid even after the offset direction was fixed. Walking the compiled walls means the polygon a probe sits on is, by construction, a real air/solid boundary of a real cell. A boundary wall's normal points OUT of its cell into the solid, so the room is at -normal. Each probe is offset that way by offset, and its HammeriteLightProbe.offset_vector records the move so HammeriteLightProbe.get_sample_position() recovers the point on the wall.
Experimental: The lightmapper's own: a game calling it can corrupt a bake.
subdivide_polygon_for_probes()
func subdivide_polygon_for_probes(poly: PackedVector3Array, normal: Vector3, spacing: float) -> Vector3[]
Grid a convex wall polygon into probe positions spacing apart, in the polygon's own plane. normal is the polygon's plane normal. The grid spans the polygon's extent along each in-plane axis separately, so a long thin wall gets even coverage. (The former per-face version squared the grid off the face's LARGEST extent, which left a wide wall sparsely sampled across its short axis.)
Experimental: The lightmapper's own: a game calling it can corrupt a bake.
aperture_at()
func aperture_at(point: Vector3, epsilon: float = 0.05) -> HammeritePortal
The opening point lies in, or null when it is on solid wall. A wall with a doorway in it is still ONE authored face - the hole is punched by the compiler, not by the author - so a ray through the doorway meets that face as though the doorway were not there. That is the right answer for questions about the authored shape and the wrong one for every question about what is actually there: picking, and anything else that should agree with what the viewport draws.
is_wall_drawn_at()
func is_wall_drawn_at(plane: Plane, point: Vector3, epsilon: float = 0.05) -> bool
Whether a compiled cell wall on plane is drawn at point. An authored air face is not drawn where a portal opens in it, nor where another room carves it away without a portal - its HammeriteBoundaryWall pieces are what is left. Asked by plane rather than by face, because a wall's face is the carved fragment's, which is not the authored face object whenever a solid cut the brush.
prepare_queries()
func prepare_queries() -> void
Build what the first question asked of the map would: the brush BVH a ray or a box is asked of, and the index a picked point is. A level's first footstep asks what floor it fell on, and on a Thief mission building these to answer was a frame of 380 ms; a compile behind a loading screen builds them there (HammeriteWorldrepCompile).
gen_instance_id()
func gen_instance_id() -> int
Generate and return a new prefab instance ID.
add_prefab_instance()
func add_prefab_instance(instance: HammeritePrefabInstance) -> void
Add a prefab instance to the map.
remove_prefab_instance()
func remove_prefab_instance(instance_id: int) -> void
Remove a prefab instance from the map.
get_prefab_instance()
func get_prefab_instance(instance_id: int) -> HammeritePrefabInstance
The placed prefab instance_id, or null if there is none.
get_brushes_in_instance()
func get_brushes_in_instance(instance_id: int) -> HammeriteBrush[]
Get all brushes belonging to a prefab instance.
get_entities_in_instance()
func get_entities_in_instance(instance_id: int) -> HammeriteEntity[]
Get all entities belonging to a prefab instance.
find_instance_for_brush()
func find_instance_for_brush(brush_id: int) -> HammeritePrefabInstance
Find the prefab instance that contains a given brush.
find_instance_for_entity()
func find_instance_for_entity(entity_id: int) -> HammeritePrefabInstance
Find the prefab instance that contains a given entity.
refresh_compiled_face_attributes()
func refresh_compiled_face_attributes() -> bool
Push authored face attributes onto the compiled boundary faces derived from them. Returns true when anything changed. Texture, lightmap scale AND the UV transform. The last was missing, which is a whole class of invisible edit: the mesh WAS rebuilt for a UV change - the signal fired, the remesh ran - but it was rebuilt from compiled faces still holding the UVs they were copied with, so nothing moved until something forced a full recompile and re-derived them. Toggling the WYSIWYG previews did it, which is a strange thing to have to know. An UNCARVED air brush is its own cell fragment, so its compiled boundary faces are the very same HammeriteFace objects and a retexture is already visible - only the mesh is stale. A CARVED one is cut into fresh fragment brushes with copied faces, so the authored change has to be carried across. A compiled face is derived from an authored one when they share a plane and the compiled face sits inside the authored brush; the plane alone is not enough, since two rooms can have coplanar walls.
authored_face_for()
func authored_face_for(face: HammeriteFace) -> HammeriteFace
The authored face a compiled one came from, or null when nothing authored is behind it. What a bake warning names is a boundary wall, and a boundary wall of a carved room is a clipped COPY - selecting it in the editor would select an object no author can reach. A face whose brush is one of the map's own is already authored: an uncarved air brush is its own fragment, and detail brushwork is never fragmented at all.
begin_detail_batch()
func begin_detail_batch() -> void
Hold back what detail arriving, moving or going makes the map redo - the world marked stale, and the detail flush against it remeshed - until the matching end_detail_batch(), which does it once for all of them. Nests. For an edit that moves many detail brushes together. One at a time, each invalidates what the one before it had just rebuilt and remeshes its neighbours, which are mostly the other brushes being moved: a terrain stroke of seventy prisms remeshed each of them a dozen times and took a second.
Experimental: The editor's view state, not part of the supported API.
end_detail_batch()
func end_detail_batch() -> void
Close a begin_detail_batch(). The outermost one marks every detail brush that arrived, moved or went inside the batch for recompiling, and remeshes the detail beside them, once.
Experimental: The editor's view state, not part of the supported API.
is_brush_shown()
func is_brush_shown(brush: HammeriteBrush) -> bool
Whether brush is shown: present in the active variant, not hidden itself, and on no hidden layer - its own or one above it.
remesh_pending_detail()
func remesh_pending_detail() -> void
Remesh the detail whose cover changed since this last ran: brushes hidden, shown or ghosted, and what stood against them. Runs by itself, deferred, once per batch - narrowing hides and shows thousands of brushes at once. Call it to have it now. Only what would leave a hole. A face that loses its cover - what covered it hidden or ghosted - is missing until its brush remeshes. A face covered again is only drawn where nobody can see it, flush under what covers it, and waits for its brush's next edit: leaving a narrow remeshes nothing.
Experimental: The editor's view state, not part of the supported API.
build_brush_bvh()
func build_brush_bvh() -> void
Build or rebuild BVH acceleration structure for all brushes Call this before lightmapping to accelerate ray-brush intersection tests. The BVH is cached and reused until invalidate_bvh() is called. When only brushes' shapes have changed since it was built, it is refitted to them instead.
invalidate_bvh()
func invalidate_bvh() -> void
Invalidate cached BVH (call when brushes are added/removed/modified)
build_occluder_bvh()
func build_occluder_bvh(within: AABB = AABB(0, 0, 0, 0, 0, 0)) -> Dictionary
Build the lightmapper's occluder set + BVH from the shadow-casting geometry (docs Phase 4 occluder model): the compiled worldrep solids (opaque structural solid - this is what seals rooms so light doesn't leak through outer walls) plus detail and brush-entity brushes (kept as occluders so the semi-transparent / water paths keep working via their per-face transmit / caustic / mask flags). The boundary cell walls are deliberately NOT occluders - they are the LIT surfaces, and the solid behind them does the occluding, so a wall patch casting toward an interior light never self-shadows (no per-face self-exclusion needed). Detail joins the LIT set later via the _get_faces hook while staying here as an occluder. Returns { "brushes": ArrayHammeriteBrush, "packed": PackedFloat32Array }: the ORDERED occluder brush list (so get_geometry_buffers packs the occluder face section in the same order the BVH's brush_index expects) and the GPU-ready BVH. Separate from build_brush_bvh(), which stays all-brushes for the acoustic raycast API. With within, only what reaches into that box: a bake of a few faces, whose lights are all inside it, has nothing to ask of rock elsewhere, and building the tree of the whole map was most of its time.
Experimental: The lightmapper's own: a game calling it can corrupt a bake.
get_worldrep_solids()
func get_worldrep_solids() -> HammeriteBrush[]
Return the shared compiled worldrep solids (Phase 1), compiling once via HammeriteSubtractiveCompiler and caching until a structural change invalidates it. Every view (2D + 3D) reads this, so a structural edit recompiles once instead of once per view. The map's own array: do not change it.
get_worldrep_solids_by_brush()
func get_worldrep_solids_by_brush() -> Dictionary[int, Array]
get_worldrep_solids() by the solid brush each was carved from, by its id. The same array from one compile to the next for a brush the compile did not reach, so whoever draws them can tell what it need not draw again. The same table to every caller until the next compile, which is not to be changed.
get_worldrep_surface_edges()
func get_worldrep_surface_edges() -> Dictionary
What a view drawing the compiled solids a fragment at a time should draw of each edge: the joins between fragments of one carved brush are not edges of anything the author made, and an edge that runs partly along such a join is drawn only where it does not. See HammeriteSubtractiveCompiler.coplanar_surface_edges().
Experimental: The editor's view state, not part of the supported API.
get_worldrep_surface_edges_of()
func get_worldrep_surface_edges_of(brush_id: int) -> Dictionary
get_worldrep_surface_edges() of the fragments of solid brush brush_id alone: what one brush's outline needs, without the whole level's gathered into one table.
Experimental: The editor's view state, not part of the supported API.
invalidate_worldrep()
func invalidate_worldrep() -> void
Invalidate the cached worldrep so the next get_worldrep_solids() recompiles, and notify views via worldrep_changed to rebuild their display. Called on any structural brush change (add / remove / geometry / tier / ownership).
invalidate_worldrep_at()
func invalidate_worldrep_at(brush: HammeriteBrush) -> void
Invalidate the worldrep for an edit to one known brush, so the next compile can keep the carves that edit cannot have reached. The dirty region is the union of where brush was and where it is: a move changes the world at both ends, and the far end is the one an author notices missing. Everything a compile derives is gated on a geometric intersection, so a carve whose own brush does not meet that region is the same carve it was. Only for an edit that moves a brush within the program. An edit that changes what the PROGRAM IS - a tier flip, a hide, a preview commit - must use invalidate_worldrep(), which keeps nothing.
worldrep_cache_path()
func worldrep_cache_path() -> String
Where this map's worldrep cache lives: beside the map, same name, .worldrep.res. Empty for a map that has never been saved, which simply has nowhere to keep one. Beside rather than inside: the cache is rewritten on every save and would otherwise churn the authored file, and "anything authored lives on a brush, a face or an entity" only stays true while derived data stays out of the map resource. Each variant is a world of its own, so each has its own: the default's is the plain name, and another's carries its layer id - .12.worldrep.res. A map saved as data (HammeriteDataFile) keeps its cache as data too - .worldrep.hcache - since a .res beside a downloaded map could run a script as surely as the map could.
worldrep_cache_path_for()
func worldrep_cache_path_for(variant_id: int) -> String
Where the saved worldrep of the variant variant_id lives, 0 the default - see worldrep_cache_path().
save_worldrep_cache()
func save_worldrep_cache(path: String = "") -> int
Save the compiled worldrep beside the map, so a cold load can skip carving it. Compiles first if it has to - there is no point saving a cache of nothing. Use a binary .res path: text costs about sixteen times as much to load and most of the win with it.
load_worldrep_cache()
func load_worldrep_cache(path: String = "") -> bool
Load a worldrep cache saved by save_worldrep_cache(). True when it matched these brushes and was taken up; false - and nothing changed - when it is missing, unreadable, or describes another version of the map, in which case the next query simply compiles as it always did. Deliberately silent about a mismatch. A stale cache here is not a bug to report, it is a cache miss: unlike the audio graph the worldrep can be rebuilt exactly, so the fallback costs time and nothing else.
save_worldrep_caches()
func save_worldrep_caches(progress: HammeriteCompileProgress = null) -> int
save_worldrep_cache() for every variant, each to its own file, without making any of them active: a production level needs each one's, and a variant nobody had on screen when the map was last saved would otherwise ship without one. The others compile on compilers of their own, made on the first call - each taking up its saved cache if that still matches - and kept, told of every edit since, so that a later save re-carves only what changed. progress hears each of those compiles. The first error is returned; the rest are still saved. Cancelled through progress, it stops at once with ERR_SKIP, writing no half-compiled variant and leaving the one it stopped in to compile whole next time. Safe on a worker while nothing edits the map, once its own worldrep is compiled: what it compiles is the other variants', which nothing on screen was drawn from.
get_slice_depth()
func get_slice_depth(axis: int) -> float
Editor depth-slice position along world axis (Vector3.AXIS_X/Y/Z) for the 2D views.
Experimental: The editor's view state, not part of the supported API.
has_slice()
func has_slice(axis: int) -> bool
Whether the 2D views are sliced along world axis at all. A map starts with no slices: one is put there by set_slice_depth() and taken away by clear_slice().
Experimental: The editor's view state, not part of the supported API.
set_slice_depth()
func set_slice_depth(axis: int, value: float) -> void
Set the editor depth-slice along world axis; emits slice_changed on a change.
Experimental: The editor's view state, not part of the supported API.
clear_slice()
func clear_slice(axis: int) -> void
Take the slice along world axis away; emits slice_changed if there was one.
Experimental: The editor's view state, not part of the supported API.
get_cells()
func get_cells() -> HammeriteCell[]
Every air HammeriteCell in the map, compiling the world if it has to. The map's own array: do not change it.
get_cells_of_brush()
func get_cells_of_brush(air_brush_id: int) -> Array
Return the HammeriteCell pieces of a single air brush, or an empty array if it has none. Note these are convex PIECES, which a carved brush has several of per room; group them by HammeriteCell.region() for rooms, or use get_region_cells(). The map's own array: do not change it.
get_region_cells()
func get_region_cells(region_key: Vector2i) -> HammeriteCell[]
Return the convex cells making up one room - the air brush and region named by region_key (HammeriteCell.key()).
get_region_keys()
func get_region_keys() -> Vector2i[]
Every distinct room in the map, as region keys (HammeriteCell.key()).
point_in_cell()
func point_in_cell(p: Vector3) -> HammeriteCell
Return the HammeriteCell that owns point p under the point-in-cell tiebreak, or null if p is in solid. When p lies in overlapping air cells, the winner is: higher HammeriteBrush.priority, then bigger owning-room volume, then lesser cell id.
native_cell_index()
func native_cell_index() -> Object
The native kernel's index of the compiled cells, which answers point_in_cell() and segment_in_air() - for another native search to ask them of directly. Null where there is none.
Experimental: A switch for the native kernel and its tests.
native_rooms_facing()
func native_rooms_facing(walls: HammeriteBoundaryWall[], owners: Dictionary[HammeriteFace, int]) -> Vector2i[]
The room each of walls faces into, as HammeriteMap3D._room_of() finds it - its owner's cell, by owners, that holds a point just behind the middle of its face - found by the native kernel for all of them at once. Empty where there is no kernel to ask.
Experimental: A switch for the native kernel and its tests.
segment_in_air()
func segment_in_air(from: Vector3, to: Vector3, epsilon: float = 0.01) -> bool
Does the segment from from to to stay in AIR the whole way? The line-of-sight primitive for anything that travels through open space - sound above all. It walks the cell graph: find the cell holding the current point, clip the ray to that convex cell to find where it leaves, step just past that boundary and ask again. Air all the way through means every step landed in another cell; the first step that lands nowhere is the wall. This is the query a raycast against the brush BVH cannot answer in a subtractive world, in either direction. Air brushes are IN that BVH, so a ray crossing open space between two rooms reports a hit on the room's own boundary; and an authored structural solid is a big block with the rooms carved out of it, so a ray from one room to another THROUGH the rock crosses none of that block's faces and reports clear. Only the compiled cells know where the air actually is. Detail brushwork inside a cell is deliberately ignored: a cabinet does not stop sound, and a cell-level answer is the one callers want. Use is_solid() for "can I stand here". Returns false when either endpoint is in solid - a listener buried in rock hears nothing by this measure, which is the right answer for the callers that ask. With the native kernel, safe to ask from several threads at once once the graph is built.
is_solid()
func is_solid(p: Vector3) -> bool
Subtractive solidity: true where point p is solid. Solid is everywhere no air cell contains p (structural solid is the complement of the air), plus wherever p lies inside a detail or brush-entity solid occupant of a cell - a detail brush does not carve the cell, so point_in_cell() still returns the room there even though the point is inside solid dressing. This is the cell-based solidity query; it replaced the former additive point-in-brush / BVH point-inside-solid checks (removed at the Phase 3 cutover).
get_portals()
func get_portals() -> HammeritePortal[]
Return every HammeritePortal in the map, compiling the graph on demand.
get_cell_property()
func get_cell_property(cell: HammeriteCell, prop_name: StringName, fallback = null) -> Variant
The effective value of extendable property prop_name for air cell cell. A cell IS an authored air brush, and HammeriteCell.air_brush_id is that brush's id, so a room's authored character is simply its brush's property - no separate per-cell storage, and nothing to re-key when the compiler splits one air brush into several regions. All regions of a brush share its properties, which is what an author means by "this room sounds like this". Returns fallback when the owning brush is gone (a stale cell) and no default is declared.
get_portals_for_cell()
func get_portals_for_cell(air_brush_id: int) -> HammeritePortal[]
The portals with air brush air_brush_id on either side (HammeritePortal.air_brush_a, HammeritePortal.air_brush_b): every opening of every room carved from that brush.
get_sky()
func get_sky() -> HammeriteSkybox
The sky over the world as it stands: the active variant's own, or else the nearest one it sits inside that has one, or else skybox.
get_sky_owner()
func get_sky_owner() -> HammeriteLayer
The variant whose sky is in effect: the active one if it has its own, or the one it takes it from. Null only for a map with no default variant.
has_own_sky()
func has_own_sky(variant: HammeriteLayer) -> bool
Whether variant has a sky of its own rather than another's. The default always has.
set_variant_sky()
func set_variant_sky(variant: HammeriteLayer, sky: HammeriteSkybox) -> void
Give variant its own sky, or none for null; the default's is skybox.
clear_variant_sky()
func clear_variant_sky(variant: HammeriteLayer) -> void
Have variant take its sky from the variant it sits inside again.
record_baked_surfaces()
func record_baked_surfaces() -> void
Write down what the bake just did, so the next recompile can hand it back. Called at the end of a bake (HammeriteLightmapper.write_lightmaps()).
Experimental: The lightmapper's own: a game calling it can corrupt a bake.
record_baked_surfaces_of()
func record_baked_surfaces_of(faces: HammeriteFace[]) -> void
record_baked_surfaces() for faces alone, adding to what is written down rather than writing it again: what a bake of a few faces does, on a map where writing every wall down took seconds.
Experimental: The lightmapper's own: a game calling it can corrupt a bake.
record_dark_surfaces()
func record_dark_surfaces(faces: HammeriteFace[]) -> void
Write down which of faces, all just baked, the bake left dark (see dark_surfaces).
Experimental: The lightmapper's own: a game calling it can corrupt a bake.
restore_baked_surfaces()
func restore_baked_surfaces() -> void
Give every boundary wall back the lighting it had before the world was recompiled. Silent about walls with no entry: a wall that has been moved or re-cut since the bake has no lighting to give.
Experimental: The lightmapper's own: a game calling it can corrupt a bake.
surface_key()
static func surface_key(face: HammeriteFace) -> String
Where a wall is, as a string a recompile reproduces: its plane, and the middle of its own polygon. The centroid is what separates the several pieces one authored wall is cut into when something stands against it - they share a plane and nothing else.
get_boundary_faces()
func get_boundary_faces() -> HammeriteFace[]
The kept cell-wall faces of the active variant: the real walls of every room, without the faces buried inside another cell (get_discarded_faces()) and with no openings cut out of them (get_boundary_walls() has those). Compiles the world if it has to. The map's own array: do not change it.
get_boundary_faces_near()
func get_boundary_faces_near(box: AABB) -> HammeriteFace[]
The boundary walls of every room that reaches into box, which is every wall that can touch one inside it - asked a room at a time rather than a wall at a time.
get_void_walls()
func get_void_walls() -> HammeriteBoundaryWall[]
Every boundary wall that HammeriteBoundaryWall.looks_into_void(), in the order get_boundary_walls() holds them. Asked by more than one view after every edit, and on the reference level asking each of a hundred thousand walls was a sixth of a second each time; so it is kept by air brush, and only the brushes a compile reached are asked again - or every one, when a face's texture was refreshed in place or what the textures are called may have changed.
get_cells_by_air()
func get_cells_by_air() -> Dictionary[int, Array]
get_cells() by the air brush they belong to, by its id. For a brush a compile did not carve again they are the array they were before it. The same table to every caller until the next compile, which is not to be changed.
get_boundary_walls_by_air()
func get_boundary_walls_by_air() -> Dictionary[int, Array]
get_boundary_walls() by the air brush they belong to, by its id. For a brush a compile did not reach they are the array they were before it.
get_boundary_faces_by_air()
func get_boundary_faces_by_air() -> Dictionary[int, Array]
get_boundary_faces() and get_discarded_faces() by the air brush they belong to, as [kept, discarded] by its id. For a brush a compile did not reach they are the arrays they were before it, which is how a view knows it need not draw them again.
get_discarded_faces()
func get_discarded_faces() -> HammeriteFace[]
The cell-wall faces buried inside another cell, which the editor hatches. The map's own array: do not change it.
is_face_hidden()
func is_face_hidden(face: HammeriteFace) -> bool
True when face is detail brushwork buried in rock or pressed against other brushwork, so that nothing draws it and nothing lights it. Always false for a cell wall, whose own culling is get_discarded_faces().
get_hidden_detail_faces()
func get_hidden_detail_faces() -> HammeriteFace[]
Every detail face is_face_hidden() is true of, for the editor's hatched visualization.
Experimental: The editor's view state, not part of the supported API.
cell_reaches_sky()
func cell_reaches_sky(cell: HammeriteCell) -> bool
True when air cell region cell can see the sky: it has a sky boundary wall of its own, or a chain of portals reaches one. This is the interior/exterior discriminator - an exterior cell receives directional light (moonlight, lightning), an interior one does not.
cell_is_open_to_sky()
func cell_is_open_to_sky(cell: HammeriteCell) -> bool
Is cell open to the sky ITSELF - does it own a sky wall - rather than merely reaching one through a chain of openings? The stricter of the two questions, and the one some consumers actually want. A room with a window onto a courtyard REACHES the sky, which is why light comes in through it, and is not outdoors: the rain is at the window, not overhead. cell_reaches_sky() answers the reachability question for the lightmapper; this answers "is this place open air" for anything that cares where a room stands rather than where its light comes from.
get_exterior_cells()
func get_exterior_cells() -> HammeriteCell[]
Every air cell region that can see the sky. See cell_reaches_sky().
get_boundary_walls()
func get_boundary_walls() -> HammeriteBoundaryWall[]
Return the kept cell walls with portal apertures already punched out, compiling the graph on demand. Preferred over get_boundary_faces() by the preview renderers: the aperture decomposition is cached here (computed once per edit) instead of recomputed per viewport redraw. The map's own array: do not change it.
get_boundary_face_owners()
func get_boundary_face_owners() -> Dictionary[HammeriteFace, int]
The air brush each kept boundary face is a wall of, by id: whose room it is, for a view that draws the walls a room at a time. Asked of the compile, because a face cut from a carved fragment has no way back to the brush it was carved from.
Experimental: The editor's view state, not part of the supported API.
get_water_surfaces()
func get_water_surfaces() -> HammeriteBoundaryWall[]
The surfaces of the map's pools - the walls a fill is seen through rather than walls of a room. Few enough in any map that asking which one a point lies under is a scan.
validate_graph()
func validate_graph() -> String[]
Validate the compiled graph and return a list of human-readable issues (empty = clean). Runs portal-conservation, an ambiguous-overlap heuristic, and a sliver-gap check (near-touching air cells that form no portal).
clear_bake_warnings()
func clear_bake_warnings() -> void
Drop the previous bake's warnings. Called at the start of a bake, so what the editor shows always describes the lighting currently on screen.
forget_bake_warnings_of()
func forget_bake_warnings_of(faces: HammeriteFace[]) -> void
Drop what the last bake said of faces, which a bake of them alone is about to say again.
Experimental: The lightmapper's own: a game calling it can corrupt a bake.
add_bake_warning()
func add_bake_warning(position: Vector3, kind: int, detail: String = "", face: HammeriteFace = null, entity: HammeriteEntity = null) -> void
Record something the bake could not do (see BakeWarningKind).
Experimental: The lightmapper's own: a game calling it can corrupt a bake.
get_bake_warnings()
func get_bake_warnings() -> HammeriteMap.BakeWarning[]
Everything the last bake could not do, for the editor to draw and the author to fix. The map's own array: do not change it.
get_graph_warning_markers()
func get_graph_warning_markers() -> HammeriteMap.GraphWarning[]
Map-space graph-validation markers, cached and lazily recomputed after each graph rebuild, so the author sees problems at edit time: a sliver gap (two air cells nearly touching but not connected), an ambiguous overlap (equal-volume overlapping cells whose owner falls to id), or a window into the void. The portal-conservation check stays text-only in validate_graph() - it fires only on an internal graph inconsistency, not an authoring mistake, so there is nothing to point the author at. The cached array itself: do not change it.
Experimental: The editor's view state, not part of the supported API.
is_bvh_valid()
func is_bvh_valid() -> bool
Check if BVH is valid and up-to-date
get_bvh_nodes()
func get_bvh_nodes() -> Array
The brush BVH's nodes as last built (HammeriteBVHBuilder.BVHNode), for CPU-side traversal. Unlike get_bvh_packed() it does not build the tree: empty, or stale, until build_brush_bvh() runs. The map's own array: do not change it.
Experimental: The editor's view state, not part of the supported API.
get_bvh_packed()
func get_bvh_packed() -> PackedFloat32Array
Get packed BVH buffer ready for GPU upload
bvh_raycast()
func bvh_raycast(origin: Vector3, direction: Vector3, max_distance: float) -> HammeriteMap.BVHHitResult
The nearest authored brush face a ray from origin along direction meets within max_distance, air brushes' faces included; result.hit is false for none. A ray against brushes, not against the world: in a carved world that is not line of sight, which is segment_in_air()'s. direction need not be normalized.
bvh_raycast_all()
func bvh_raycast_all(origin: Vector3, direction: Vector3, max_distance: float) -> HammeriteMap.BVHHitResult[]
Every authored brush face the ray meets, nearest first, as bvh_raycast() finds the nearest: for counting how much brushwork a line crosses. direction need not be normalized.
bvh_query_bounds()
func bvh_query_bounds(aabb: AABB) -> int[]
Query for brush IDs intersecting an axis-aligned bounding box Returns an array of brush IDs that intersect the given AABB. Useful for voxel chunk optimization during acoustic generation.
merge_brushes()
func merge_brushes(input_brushes: HammeriteBrush[], texture: Texture2D) -> HammeriteBrush
Merge multiple brushes into a single convex brush. Returns the merged brush without adding it to the map. The caller is responsible for adding the result and removing source brushes if needed.
group_brushes()
func group_brushes(input_brushes: HammeriteBrush[]) -> int
Group the given brushes by assigning them a new group ID. Returns the new group ID, or 0 if grouping failed.
ungroup_brushes()
func ungroup_brushes(input_brushes: HammeriteBrush[]) -> void
Ungroup the given brushes by removing their group IDs. Sets group_id to 0 for all provided brushes.
get_custom_data()
func get_custom_data(key: String, default = null) -> Variant
What custom_data holds under key, or default for nothing there. Asked of the map each time rather than of a dictionary held on to: switching variant replaces it.
set_custom_data()
func set_custom_data(key: String, value) -> void
Keep value in custom_data under key and emit custom_data_changed: the one way to change it that what depends on it hears.
unknown_classes()
func unknown_classes() -> PackedStringArray
The classes this game does not have that the map is carrying from its file, each once - an addon's baked data, kept while the addon is not loaded (HammeriteUnknownData). Empty for a map that needs nothing it lacks. Walks the whole map; see HammeriteDataFile.unknown_classes().
class AuthoredFaces
The faces of a map's authored brushes, by the plane a compiled wall lies on when it came from one - so asking which a wall came from looks at the few faces on its plane, not at every brush in the map. Asked of every wall, a scan was walls times brushes: on the reference level a billion containment tests, run by retexturing any wall, and the editor did not come back.
Methods
| Returns | Method |
|---|---|
HammeriteFace | behind(face: HammeriteFace, at: Vector3) |
Plane | wall_plane(brush: HammeriteBrush, authored: HammeriteFace) static |
Vector2i | key_of(plane: Plane) static |
Vector2i[] | keys_of(plane: Plane) static |
Method descriptions
behind()
func behind(face: HammeriteFace, at: Vector3) -> HammeriteFace
The authored face face was derived from, sampled at at, or null.
wall_plane()
static func wall_plane(brush: HammeriteBrush, authored: HammeriteFace) -> Plane
The plane a compiled wall lies on when it came from authored, a face of brush.
key_of()
static func key_of(plane: Plane) -> Vector2i
Which way the plane mostly faces, and roughly where it is.
keys_of()
static func keys_of(plane: Plane) -> Vector2i[]
Every key_of() a plane equal to this one could have, by Plane.is_equal_approx(): where two axes nearly tie, or the distance is nearly halfway between two whole numbers, a plane a hair away falls under the other.
class BVHHitResult
Result structure for BVH raycasts
Properties
| Type | Name | Default |
|---|---|---|
bool | hit | false |
Vector3 | point | Vector3(0, 0, 0) |
Vector3 | normal | Vector3(0, 0, 0) |
float | distance | inf |
int | brush_id | -1 |
int | face_id | -1 |
String | texture | "" |
Property descriptions
hit
var hit: bool = false
Whether the ray met a face. When false the other fields keep their defaults.
point
var point: Vector3 = Vector3(0, 0, 0)
Where the ray met the face, in map space.
normal
var normal: Vector3 = Vector3(0, 0, 0)
The face's normal, pointing out of its own brush - into the rock, for an air brush.
distance
var distance: float = inf
How far along the ray the hit is, in map units.
brush_id
var brush_id: int = -1
The HammeriteBrush.id of the brush hit, or -1.
face_id
var face_id: int = -1
The index of the face hit in that brush's HammeriteBrush.faces, or -1.
texture
var texture: String = ""
The texture name of the face hit, or empty.
class BakeWarning
A single thing the last bake could not do: where it happened, which kind, and a human-readable detail (the offending size, the number of lights) for the editor and the log.
Properties
| Type | Name | Default |
|---|---|---|
Vector3 | position | |
int | kind | |
String | detail | |
HammeriteFace | face | |
HammeriteEntity | entity |
Property descriptions
position
var position: Vector3
Where it happened, in map space.
kind
var kind: int
What went wrong.
detail
var detail: String
The offending figure in words (a size, a count of lights), for the log and the editor; may be empty.
face
var face: HammeriteFace
What the warning is about, so the editor can select it and put the views on it rather than leaving the author to find a cross floating in the middle of the map. One or the other: a face's warning names its face, a prop's names its entity.
entity
var entity: HammeriteEntity
The prop the warning is about, or null for a face's warning; see face.
class GraphWarning
A single graph-validation warning: a world-space position and its WarningKind.
Properties
| Type | Name | Default |
|---|---|---|
Vector3 | position | |
int | kind | |
HammeriteBrush | brush_a | |
HammeriteBrush | brush_b | |
HammeriteFace | face |
Property descriptions
position
var position: Vector3
Where to draw the marker, in map space.
kind
var kind: int
What is wrong.
brush_a
var brush_a: HammeriteBrush
The rooms the warning is between. SLIVER_GAP and AMBIGUOUS_OVERLAP are about a PAIR of them and the position is only the midpoint between the two; VOID_WINDOW is about one wall, so it names the room that wall bounds and leaves brush_b null. Authored air brushes, so the editor can select them.
brush_b
var brush_b: HammeriteBrush
The second room of the pair, or null for a VOID_WINDOW; see brush_a.
face
var face: HammeriteFace
The wall the warning is about, where it is about one - VOID_WINDOW. A boundary face is derived and has no id of its own, so it is handed over rather than named.
class LightingSnapshot
The lighting on the map at one moment, to be put back exactly by restore_lighting(). It holds the faces themselves, so it is for the session it was taken in and is never saved.
Properties
Property descriptions
faces
var faces: Dictionary[HammeriteFace, Array] = {}
Face -> [lightmap, uv2, lightmap scale], for every authored face and every boundary wall. The UV2 array is the face's own: the bake hands a face a new one rather than writing into it.
baked_surfaces
var baked_surfaces: Dictionary = {}
A copy of the map's HammeriteMap.baked_surfaces.
dark_surfaces
var dark_surfaces: Dictionary = {}
A copy of the map's HammeriteMap.dark_surfaces.
light_lightmaps
var light_lightmaps: Dictionary[int, Array] = {}
A deep copy of the map's HammeriteMap.light_lightmaps.
mesh_entity_lightmaps
var mesh_entity_lightmaps: Dictionary[int, HammeriteBakedLightmap] = {}
A copy of the map's HammeriteMap.mesh_entity_lightmaps.
mesh_uv2s
var mesh_uv2s: Dictionary[int, PackedVector2Array] = {}
A copy of the map's HammeriteMap.mesh_uv2s.
baked_lights
var baked_lights: Dictionary[int, HammeriteBakedLight] = {}
A copy of the map's HammeriteMap.baked_lights; the lights themselves are shared, not copied.
baked_lights_by_group
var baked_lights_by_group: Dictionary[StringName, HammeriteBakedLight] = {}
A copy of the map's HammeriteMap.baked_lights_by_group.
probe_set
var probe_set: HammeriteLightProbeSet = null
The map's HammeriteMap.probe_set itself, not a copy; probes holds what it carried.
probes
var probes: Dictionary[HammeriteLightProbe, Dictionary] = {}
Probe -> its stored properties. A bake that keeps the probes it has relights them in place.
warnings
var warnings: HammeriteMap.BakeWarning[] = []
The bake warnings on the map when it was taken (HammeriteMap.get_bake_warnings()).
class VariantWorld
A variant's world compiled on a compiler of its own, beside the active one's, with the edits it has not caught up with yet.
Properties
| Type | Name | Default |
|---|---|---|
HammeriteSubtractiveCompiler | compiler | null |
AABB[] | dirty_boxes | [] |
bool | dirty_all | true |
Property descriptions
compiler
var compiler: HammeriteSubtractiveCompiler = null
The compiler holding this variant's carves.
dirty_boxes
var dirty_boxes: AABB[] = []
Map-space regions edited since compiler last compiled.
dirty_all
var dirty_all: bool = true
Whether the next compile has to carve everything rather than dirty_boxes alone.