* layers system (stored in comments of .map file)

menus	* View->Layers Browser (L)
	* Edit->Expand Selection To Layers (ctrl + q)
	* shift + q: move-to LayersMenu
closes #133
This commit is contained in:
Garux
2025-08-31 16:25:12 +05:00
parent 581dd79acb
commit 1599d0a64b
40 changed files with 1042 additions and 82 deletions
+2 -2
View File
@@ -634,9 +634,9 @@ class PicoModelNode : public scene::Node::Symbiot, public scene::Instantiable
public:
typedef LazyStatic<TypeCasts> StaticTypeCasts;
PicoModelNode() : m_node( this, this, StaticTypeCasts::instance().get() ){
PicoModelNode() : m_node( this, this, StaticTypeCasts::instance().get(), nullptr ){
}
PicoModelNode( const AssScene scene ) : m_node( this, this, StaticTypeCasts::instance().get() ), m_picomodel( scene ){
PicoModelNode( const AssScene scene ) : m_node( this, this, StaticTypeCasts::instance().get(), nullptr ), m_picomodel( scene ){
}
void release(){
+2 -2
View File
@@ -657,7 +657,7 @@ public:
}
Doom3GroupNode( EntityClass* eclass ) :
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), nullptr ),
m_contained( eclass, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSet::BoundsChangedCaller( m_instances ), InstanceSetEvaluateTransform<Doom3GroupInstance>::Caller( m_instances ) ){
construct();
}
@@ -666,7 +666,7 @@ public:
scene::Instantiable( other ),
scene::Cloneable( other ),
scene::Traversable::Observer( other ),
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), nullptr ),
m_contained( other.m_contained, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSet::BoundsChangedCaller( m_instances ), InstanceSetEvaluateTransform<Doom3GroupInstance>::Caller( m_instances ) ){
construct();
}
+2 -2
View File
@@ -437,7 +437,7 @@ public:
}
EclassModelNode( EntityClass* eclass ) :
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), GlobalSceneGraph().currentLayer() ),
m_contained( eclass, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSetEvaluateTransform<EclassModelInstance>::Caller( m_instances ) ){
construct();
}
@@ -446,7 +446,7 @@ public:
scene::Instantiable( other ),
scene::Cloneable( other ),
scene::Traversable::Observer( other ),
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), other.m_node.m_layer ),
m_contained( other.m_contained, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSetEvaluateTransform<EclassModelInstance>::Caller( m_instances ) ){
construct();
}
+2 -2
View File
@@ -388,14 +388,14 @@ public:
}
GenericEntityNode( EntityClass* eclass ) :
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), GlobalSceneGraph().currentLayer() ),
m_contained( eclass, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSetEvaluateTransform<GenericEntityInstance>::Caller( m_instances ) ){
}
GenericEntityNode( const GenericEntityNode& other ) :
scene::Node::Symbiot( other ),
scene::Instantiable( other ),
scene::Cloneable( other ),
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), other.m_node.m_layer ),
m_contained( other.m_contained, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSetEvaluateTransform<GenericEntityInstance>::Caller( m_instances ) ){
}
void release(){
+2 -2
View File
@@ -445,7 +445,7 @@ public:
}
GroupNode( EntityClass* eclass ) :
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), nullptr ),
m_contained( eclass, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSetEvaluateTransform<GroupInstance>::Caller( m_instances ) ){
construct();
}
@@ -454,7 +454,7 @@ public:
scene::Instantiable( other ),
scene::Cloneable( other ),
scene::Traversable::Observer( other ),
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), nullptr ),
m_contained( other.m_contained, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSetEvaluateTransform<GroupInstance>::Caller( m_instances ) ){
construct();
}
+2 -2
View File
@@ -2044,7 +2044,7 @@ public:
}
LightNode( EntityClass* eclass ) :
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), GlobalSceneGraph().currentLayer() ),
m_contained( eclass, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSet::BoundsChangedCaller( m_instances ), InstanceSetEvaluateTransform<LightInstance>::Caller( m_instances ) ){
construct();
}
@@ -2053,7 +2053,7 @@ public:
scene::Instantiable( other ),
scene::Cloneable( other ),
scene::Traversable::Observer( other ),
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), other.m_node.m_layer ),
m_contained( other.m_contained, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSet::BoundsChangedCaller( m_instances ), InstanceSetEvaluateTransform<LightInstance>::Caller( m_instances ) ){
construct();
}
+2 -2
View File
@@ -491,7 +491,7 @@ public:
}
MiscModelNode( EntityClass* eclass ) :
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), GlobalSceneGraph().currentLayer() ),
m_contained( eclass, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSetEvaluateTransform<MiscModelInstance>::Caller( m_instances ) ){
construct();
}
@@ -500,7 +500,7 @@ public:
scene::Instantiable( other ),
scene::Cloneable( other ),
scene::Traversable::Observer( other ),
m_node( this, this, StaticTypeCasts::instance().get() ),
m_node( this, this, StaticTypeCasts::instance().get(), other.m_node.m_layer ),
m_contained( other.m_contained, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSetEvaluateTransform<MiscModelInstance>::Caller( m_instances ) ){
construct();
}
+108 -5
View File
@@ -32,6 +32,98 @@
#include "string/string.h"
#include "stringio.h"
#include "eclasslib.h"
#include "layers.h"
class LayersParser
{
Layers& m_layers;
std::vector<Layer::iterator> m_layersVec; // vector to access layers by index
int m_currentLayer = LAYERIDX0;
public:
LayersParser( scene::Node& root ) : m_layers( *Node_getLayers( root ) ){
}
void construct_tree(){
{
auto& list = m_layers.m_children;
if( list.size() > 1 ) // something was parsed, remove default inserted layer
list.pop_front();
m_layersVec.reserve( list.size() );
for( auto it = list.begin(); it != list.end(); ++it )
m_layersVec.push_back( it );
// check safety // note possible circular reference is not checked
if( std::ranges::find( list, LAYERIDXPARENT, &Layer::m_parentIndex ) == list.cend() ){
globalErrorStream() << "No layers in the root\n";
list.front().m_parentIndex = LAYERIDXPARENT;
}
}
for( auto it : m_layersVec ){
// check safety
if( it->m_parentIndex < LAYERIDXPARENT || it->m_parentIndex >= static_cast<int>( m_layersVec.size() ) ){
globalErrorStream() << it->m_parentIndex << " parent layer index out of bounds\n";
it->m_parentIndex = LAYERIDXPARENT;
}
if( it->m_parentIndex > LAYERIDXPARENT && m_layersVec[ it->m_parentIndex ] == it ){
globalErrorStream() << it->m_parentIndex << " parent layer index self reference\n";
it->m_parentIndex = LAYERIDXPARENT;
}
// link & reference parent
Layer& parent = it->m_parentIndex == LAYERIDXPARENT? m_layers : *m_layersVec[ it->m_parentIndex ];
parent.m_children.splice( parent.m_children.cend(), m_layers.m_children, it );
it->m_parent = &parent;
}
m_layers.m_currentLayer = &m_layers.m_children.front();
}
bool read_layers( Tokeniser& tokeniser ){
for (;; )
{
tokeniser.nextLine();
const char *token;
if( !( token = tokeniser.getToken() ) )
return false;
if( !string_equal( token, "layerdef" ) ){
tokeniser.ungetToken();
return true;
}
if( !( token = tokeniser.getToken() ) ){
Tokeniser_unexpectedError( tokeniser, token, "#layer-name" );
return false;
}
auto& layer = m_layers.m_children.emplace_back( token, nullptr ); // initially construct plain list
if( !Tokeniser_getInteger( tokeniser, layer.m_parentIndex )
|| !Tokeniser_getInteger( tokeniser, layer.m_color[ 0 ] )
|| !Tokeniser_getInteger( tokeniser, layer.m_color[ 1 ] )
|| !Tokeniser_getInteger( tokeniser, layer.m_color[ 2 ] ) )
return false;
Tokeniser_skipToNextLine( tokeniser ); // skip possible format extension
}
}
bool read_layer( Tokeniser& tokeniser ){
const char *token = tokeniser.getToken();
if ( token == nullptr )
return false;
if( !string_equal( token, "layer" ) ){
tokeniser.ungetToken();
return true;
}
if( !Tokeniser_getInteger( tokeniser, m_currentLayer ) ){
return false;
}
else if( m_currentLayer <= LAYERIDXPARENT || m_currentLayer >= static_cast<int>( m_layersVec.size() ) ){
globalErrorStream() << m_currentLayer << " primitive layer index out of bounds\n";
m_currentLayer = LAYERIDX0;
}
Tokeniser_skipToNextLine( tokeniser ); // skip possible format extension
return true;
}
Layer* getCurrentLayer(){
return m_layersVec[ m_currentLayer ].operator->();
}
};
inline MapImporter* Node_getMapImporter( scene::Node& node ){
return NodeTypeCast<MapImporter>::cast( node );
@@ -52,7 +144,7 @@ NodeSmartReference Entity_create( EntityCreator& entityTable, EntityClass* entit
return NodeSmartReference( entity );
}
NodeSmartReference Entity_parseTokens( Tokeniser& tokeniser, EntityCreator& entityTable, const PrimitiveParser& parser, int index ){
NodeSmartReference Entity_parseTokens( Tokeniser& tokeniser, EntityCreator& entityTable, const PrimitiveParser& parser, int index, LayersParser& layersParser ){
NodeSmartReference entity( g_nullNode );
KeyValues keyValues;
const char* classname = "";
@@ -61,8 +153,8 @@ NodeSmartReference Entity_parseTokens( Tokeniser& tokeniser, EntityCreator& enti
while ( 1 )
{
tokeniser.nextLine();
const char* token = tokeniser.getToken();
if ( token == 0 ) {
const char* token;
if ( !layersParser.read_layer( tokeniser ) || !( token = tokeniser.getToken() ) ) {
Tokeniser_unexpectedError( tokeniser, token, "#entity-token" );
return g_nullNode;
}
@@ -86,6 +178,7 @@ NodeSmartReference Entity_parseTokens( Tokeniser& tokeniser, EntityCreator& enti
globalErrorStream() << "brush " << count_primitives << ": parse error\n";
return g_nullNode;
}
primitive.get().m_layer = layersParser.getCurrentLayer();
scene::Traversable* traversable = Node_getTraversable( entity );
if ( Node_getEntity( entity )->isContainer() && traversable != 0 ) {
@@ -116,15 +209,23 @@ NodeSmartReference Entity_parseTokens( Tokeniser& tokeniser, EntityCreator& enti
}
void Map_Read( scene::Node& root, Tokeniser& tokeniser, EntityCreator& entityTable, const PrimitiveParser& parser ){
LayersParser layersParser( root );
if( !layersParser.read_layers( tokeniser ) ){
layersParser.construct_tree(); // contruct anytime to have at least one layer, e.g. when empty .map
return;
}
layersParser.construct_tree();
int count_entities = 0;
for (;; )
{
tokeniser.nextLine();
if ( !tokeniser.getToken() ) { // { or 0
if ( !layersParser.read_layer( tokeniser ) || !tokeniser.getToken() ) { // { or 0
break;
}
Layer *entityLayer = layersParser.getCurrentLayer();
NodeSmartReference entity( Entity_parseTokens( tokeniser, entityTable, parser, count_entities ) );
NodeSmartReference entity( Entity_parseTokens( tokeniser, entityTable, parser, count_entities, layersParser ) );
if ( entity == g_nullNode ) {
globalErrorStream() << "entity " << count_entities << ": parse error\n";
@@ -132,6 +233,8 @@ void Map_Read( scene::Node& root, Tokeniser& tokeniser, EntityCreator& entityTab
}
Node_getTraversable( root )->insert( entity );
if( !Node_getEntity( entity )->isContainer() )
entity.get().m_layer = entityLayer;
++count_entities;
}
+6 -6
View File
@@ -101,7 +101,7 @@ public:
return g_nullNode;
}
void readGraph( scene::Node& root, TextInputStream& inputStream, EntityCreator& entityTable ) const {
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewSimpleTokeniser( inputStream );
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewMapTokeniser( inputStream );
tokeniser.nextLine();
if ( !Tokeniser_parseToken( tokeniser, "Version" ) ) {
return;
@@ -172,7 +172,7 @@ public:
return g_nullNode;
}
void readGraph( scene::Node& root, TextInputStream& inputStream, EntityCreator& entityTable ) const {
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewSimpleTokeniser( inputStream );
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewMapTokeniser( inputStream );
tokeniser.nextLine();
if ( !Tokeniser_parseToken( tokeniser, "Version" ) ) {
return;
@@ -291,7 +291,7 @@ public:
}
void readGraph( scene::Node& root, TextInputStream& inputStream, EntityCreator& entityTable ) const {
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewSimpleTokeniser( inputStream );
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewMapTokeniser( inputStream );
m_formatDetected = false;
Map_Read( root, tokeniser, entityTable, *this );
tokeniser.release();
@@ -368,7 +368,7 @@ public:
return g_nullNode;
}
void readGraph( scene::Node& root, TextInputStream& inputStream, EntityCreator& entityTable ) const {
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewSimpleTokeniser( inputStream );
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewMapTokeniser( inputStream );
m_formatDetected = false;
Map_Read( root, tokeniser, entityTable, *this );
tokeniser.release();
@@ -412,7 +412,7 @@ public:
return g_nullNode;
}
void readGraph( scene::Node& root, TextInputStream& inputStream, EntityCreator& entityTable ) const {
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewSimpleTokeniser( inputStream );
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewMapTokeniser( inputStream );
Map_Read( root, tokeniser, entityTable, *this );
tokeniser.release();
}
@@ -487,7 +487,7 @@ public:
return g_nullNode;
}
void readGraph( scene::Node& root, TextInputStream& inputStream, EntityCreator& entityTable ) const {
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewSimpleTokeniser( inputStream );
Tokeniser& tokeniser = GlobalScripLibModule::getTable().m_pfnNewMapTokeniser( inputStream );
m_formatDetected = false;
Map_Read( root, tokeniser, entityTable, *this );
tokeniser.release();
+36 -3
View File
@@ -24,6 +24,31 @@
#include "ientity.h"
#include "iscriplib.h"
#include "scenelib.h"
#include "layers.h"
void Layers_Write( Layers& layers, TokenWriter& writer ){
layers.forEach( [&]( Layer& layer ){
writer.writeToken( "//@$&" );
writer.writeToken( "layerdef" );
writer.writeString( layer.m_name.c_str() );
writer.writeInteger( layer.m_parent->m_ownIndex );
writer.writeInteger( layer.m_color[ 0 ] );
writer.writeInteger( layer.m_color[ 1 ] );
writer.writeInteger( layer.m_color[ 2 ] );
writer.nextLine();
} );
}
void Layer_Write( const Layer *layer, int& currentLayer, TokenWriter& writer ){
if( layer != nullptr && currentLayer != layer->m_ownIndex ){
currentLayer = layer->m_ownIndex;
writer.writeToken( "//@$&" );
writer.writeToken( "layer" );
writer.writeInteger( currentLayer );
writer.nextLine();
}
}
inline MapExporter* Node_getMapExporter( scene::Node& node ){
return NodeTypeCast<MapExporter>::cast( node );
@@ -61,9 +86,13 @@ class WriteTokensWalker : public scene::Traversable::Walker
mutable Stack<bool> m_stack;
TokenWriter& m_writer;
bool m_ignorePatches;
mutable int m_currentLayer = LAYERIDX0;
public:
WriteTokensWalker( TokenWriter& writer, bool ignorePatches )
: m_writer( writer ), m_ignorePatches( ignorePatches ){
WriteTokensWalker( TokenWriter& writer, bool ignorePatches, Layers& layers )
: m_writer( writer ), m_ignorePatches( ignorePatches )
{
layers.update_ownIndices();
Layers_Write( layers, m_writer );
}
bool pre( scene::Node& node ) const {
m_stack.push( false );
@@ -80,6 +109,8 @@ public:
m_writer.writeUnsigned( g_count_entities++ );
m_writer.nextLine();
Layer_Write( node.m_layer, m_currentLayer, m_writer );
m_writer.writeToken( "{" );
m_writer.nextLine();
m_stack.top() = true;
@@ -96,6 +127,8 @@ public:
m_writer.writeUnsigned( g_count_brushes++ );
m_writer.nextLine();
Layer_Write( node.m_layer, m_currentLayer, m_writer );
exporter->exportTokens( m_writer );
}
}
@@ -113,5 +146,5 @@ public:
void Map_Write( scene::Node& root, GraphTraversalFunc traverse, TokenWriter& writer, bool ignorePatches ){
g_count_entities = 0;
traverse( root, WriteTokensWalker( writer, ignorePatches ) );
traverse( root, WriteTokensWalker( writer, ignorePatches, *Node_getLayers( root ) ) );
}
+1 -1
View File
@@ -456,7 +456,7 @@ public:
typedef LazyStatic<TypeCasts> StaticTypeCasts;
ModelNode() : m_node( this, this, StaticTypeCasts::instance().get() ){
ModelNode() : m_node( this, this, StaticTypeCasts::instance().get(), nullptr ){
}
Model& model(){
+2 -2
View File
@@ -585,9 +585,9 @@ class PicoModelNode : public scene::Node::Symbiot, public scene::Instantiable
public:
typedef LazyStatic<TypeCasts> StaticTypeCasts;
PicoModelNode() : m_node( this, this, StaticTypeCasts::instance().get() ){
PicoModelNode() : m_node( this, this, StaticTypeCasts::instance().get(), nullptr ){
}
PicoModelNode( picoModel_t* model ) : m_node( this, this, StaticTypeCasts::instance().get() ), m_picomodel( model ){
PicoModelNode( picoModel_t* model ) : m_node( this, this, StaticTypeCasts::instance().get(), nullptr ), m_picomodel( model ){
}
void release(){