2026-10-28 20:47:04 +00:00
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using Godot;
|
|
|
|
|
using System;
|
|
|
|
|
|
|
|
|
|
using Rokojori.Extensions;
|
|
|
|
|
namespace Rokojori
|
|
|
|
|
{
|
|
|
|
|
/** Base class for anything that draws into a HeightMapGenerator's map.
|
|
|
|
|
|
|
|
|
|
A Node3D, so a layer is placed, rotated and scaled in the 3D viewport
|
|
|
|
|
rather than in texel coordinates - that is the whole point of the design.
|
|
|
|
|
The generator converts the layer's world position into texels when it
|
|
|
|
|
draws.
|
|
|
|
|
|
|
|
|
|
Subclasses only implement RenderToHeightMap(). Everything about <marked>when
|
|
|
|
|
</marked> that happens is handled here: this class watches its own transform
|
|
|
|
|
and visibility and flags the nearest HeightMapGenerator above it, which
|
|
|
|
|
re-renders once on the next frame.
|
|
|
|
|
|
|
|
|
|
Two kinds sit under this: HeightMapGeneratorLayer for procedural shapes with
|
|
|
|
|
a blend mode, and - later - layers that are not shapes at all, like an
|
|
|
|
|
image or a nested map, which is why the blend mode is not here.
|
|
|
|
|
*/
|
|
|
|
|
[Tool]
|
|
|
|
|
public abstract partial class HeightMapDrawLayer:Node3D
|
|
|
|
|
{
|
|
|
|
|
/** Draws this layer into `generator`'s map.
|
|
|
|
|
|
|
|
|
|
Called in tree order during a render, with the map already allocated and
|
|
|
|
|
cleared. Implementations are expected to go through the generator for
|
|
|
|
|
coordinate conversion and for the blit itself - see
|
|
|
|
|
CircleHeightMapGeneratorLayer for the shape case.
|
|
|
|
|
*/
|
|
|
|
|
public abstract void RenderToHeightMap( HeightMapGenerator generator );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* FINDING THE GENERATOR
|
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
|
Resolved lazily rather than in _Ready, because _Ready order between a
|
|
|
|
|
layer and its generator is not something a layer should depend on, and
|
|
|
|
|
because reparenting a layer in the editor has to keep working.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
HeightMapGenerator _generator;
|
|
|
|
|
|
|
|
|
|
public HeightMapGenerator generator
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if ( IsStillOurGenerator() )
|
|
|
|
|
{
|
|
|
|
|
return _generator;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_generator = NodesWalker.Get().GetParentWithType<HeightMapGenerator>( this );
|
|
|
|
|
|
|
|
|
|
return _generator;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** The cached reference survives only while it is still a live node and
|
|
|
|
|
still above us - a freed generator or a reparented layer has to walk
|
|
|
|
|
again, and IsInstanceValid is what keeps the freed case from throwing.
|
|
|
|
|
*/
|
|
|
|
|
bool IsStillOurGenerator()
|
|
|
|
|
{
|
|
|
|
|
return _generator != null
|
|
|
|
|
&& GodotObject.IsInstanceValid( _generator )
|
|
|
|
|
&& _generator.IsInsideTree()
|
|
|
|
|
&& IsInsideTree()
|
|
|
|
|
&& _generator.IsAncestorOf( this );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Flags the generator above this layer for a re-render. Safe to call as
|
|
|
|
|
often as the editor emits changes - the generator collapses them into one
|
|
|
|
|
render per frame.
|
|
|
|
|
*/
|
|
|
|
|
protected void SetDirty()
|
|
|
|
|
{
|
2026-10-29 13:31:38 +00:00
|
|
|
#if TOOLS
|
|
|
|
|
// A radius or a size edited in the inspector moves the gizmo handles, and
|
|
|
|
|
// nothing else would tell the editor to redraw them - a transform change
|
|
|
|
|
// does that on its own, an exported value does not.
|
|
|
|
|
if ( Engine.IsEditorHint() )
|
|
|
|
|
{
|
|
|
|
|
UpdateGizmos();
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-10-28 20:47:04 +00:00
|
|
|
var g = generator;
|
|
|
|
|
|
|
|
|
|
if ( g == null )
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g.SetDirty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* CHANGE NOTIFICATION
|
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
|
Transform changes need SetNotifyTransform( true ) - Godot does not send
|
|
|
|
|
NotificationTransformChanged otherwise, and without it dragging a layer in
|
|
|
|
|
the viewport would not update the map at all.
|
|
|
|
|
|
|
|
|
|
Visibility rides on the eye icon in the scene tree, so the layer has no
|
|
|
|
|
"enabled" export of its own.
|
|
|
|
|
|
|
|
|
|
Exported values on a subclass are the subclass's business: a plain [Export]
|
|
|
|
|
field edited in the inspector notifies nothing, so every one of them has to
|
|
|
|
|
be a property whose setter calls SetDirty(). That is a convention, not
|
|
|
|
|
something this class can enforce.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
public override void _Ready()
|
|
|
|
|
{
|
|
|
|
|
SetNotifyTransform( true );
|
|
|
|
|
SetDirty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void _EnterTree()
|
|
|
|
|
{
|
|
|
|
|
// A fresh walk next time - the layer may have been reparented under a
|
|
|
|
|
// different generator.
|
|
|
|
|
_generator = null;
|
|
|
|
|
SetDirty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void _ExitTree()
|
|
|
|
|
{
|
|
|
|
|
// Flag the generator it is leaving, so the map loses the layer's
|
|
|
|
|
// contribution. Taken before the cached reference is dropped.
|
|
|
|
|
SetDirty();
|
|
|
|
|
|
|
|
|
|
_generator = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void _Notification( int what )
|
|
|
|
|
{
|
|
|
|
|
base._Notification( what );
|
|
|
|
|
|
|
|
|
|
if ( what == NotificationTransformChanged || what == NotificationVisibilityChanged )
|
|
|
|
|
{
|
|
|
|
|
SetDirty();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|