diff --git a/Runtime/Godot/Scenes/Header/SceneFileHeader.cs b/Runtime/Godot/Scenes/Header/SceneFileHeader.cs new file mode 100644 index 0000000..f4a78fa --- /dev/null +++ b/Runtime/Godot/Scenes/Header/SceneFileHeader.cs @@ -0,0 +1,11 @@ +using Godot; +using System.Collections.Generic; + +namespace Rokojori +{ + public class SceneFileHeader + { + public string type; + public List attributes = new List(); + } +} \ No newline at end of file diff --git a/Runtime/Godot/Scenes/Header/SceneFileHeaderAttribute.cs b/Runtime/Godot/Scenes/Header/SceneFileHeaderAttribute.cs new file mode 100644 index 0000000..19bb2ea --- /dev/null +++ b/Runtime/Godot/Scenes/Header/SceneFileHeaderAttribute.cs @@ -0,0 +1,94 @@ +using Godot; +using System.Collections.Generic; +using System; + +namespace Rokojori +{ + public class SceneFileHeaderAttribute + { + protected string _attributeName; + + public static SceneFileHeaderAttribute Create( string value ) + { + var an = new SceneFileHeaderAttribute(); + an._attributeName = value; + + return an; + } + + public SceneFileNamedValue GetAttribute( SceneFileHeader header ) + { + return header.attributes.Find( a => a.name == _attributeName ); + } + + public string Get( SceneFileHeader header, string alternative = null ) + { + var attribute = GetAttribute( header ); + + if ( attribute == null ) + { + return alternative; + } + + return attribute.value.Substring( 1, attribute.value.Length - 2 ); + } + + public double GetNumberValue( SceneFileHeader header, double alternative = 0 ) + { + var attribute = GetAttribute( header ); + + if ( attribute == null ) + { + return alternative; + } + + return RegexUtility.ParseDouble( attribute.value ); + } + + + } + + public class SceneFileHeaderAttributeValue:SceneFileHeaderAttribute + { + public virtual void GetValue( SceneFileHeader header ) + { + } + } + + public class SceneFileHeaderAttributeValue:SceneFileHeaderAttributeValue + { + T _value; + + public T value => _value; + + Func _getter; + + public SceneFileHeaderAttributeValue( string name, Func getter ) + { + _attributeName = name; + _getter = getter; + } + + public override void GetValue( SceneFileHeader header ) + { + _value = _getter( this, header ); + } + + + } + + public class SFHStringAttribute:SceneFileHeaderAttributeValue + { + public SFHStringAttribute( string name ) + :base( name, ( att, entry ) => att.Get( entry ) ) + {} + + } + + public class SFHNumberAttribute:SceneFileHeaderAttributeValue + { + public SFHNumberAttribute( string name ) + :base( name, ( att, entry ) => att.GetNumberValue( entry ) ) + {} + } +} \ No newline at end of file diff --git a/Runtime/Godot/Scenes/Objects/EditableSFO.cs b/Runtime/Godot/Scenes/Objects/EditableSFO.cs new file mode 100644 index 0000000..10701f7 --- /dev/null +++ b/Runtime/Godot/Scenes/Objects/EditableSFO.cs @@ -0,0 +1,13 @@ +using Godot; +using System.Collections.Generic; + +namespace Rokojori +{ + public class EditableSFO:SceneFileObject + { + public static readonly string headerType = "editable"; + + public readonly SFHStringAttribute path = new SFHStringAttribute( "path" ); + + } +} \ No newline at end of file diff --git a/Runtime/Godot/Scenes/Objects/ExtResourceSFO.cs b/Runtime/Godot/Scenes/Objects/ExtResourceSFO.cs new file mode 100644 index 0000000..50eb16d --- /dev/null +++ b/Runtime/Godot/Scenes/Objects/ExtResourceSFO.cs @@ -0,0 +1,15 @@ +using Godot; +using System.Collections.Generic; + +namespace Rokojori +{ + public class ExtResourceSFO:SceneFileObject + { + public static readonly string headerType = "ext_resource"; + + public readonly SFHStringAttribute path = new SFHStringAttribute( "path" ); + public readonly SFHStringAttribute uid = new SFHStringAttribute( "uid" ); + public readonly SFHStringAttribute type = new SFHStringAttribute( "type" ); + + } +} \ No newline at end of file diff --git a/Runtime/Godot/Scenes/Objects/GDSceneSFO.cs b/Runtime/Godot/Scenes/Objects/GDSceneSFO.cs new file mode 100644 index 0000000..1944a78 --- /dev/null +++ b/Runtime/Godot/Scenes/Objects/GDSceneSFO.cs @@ -0,0 +1,15 @@ +using Godot; +using System.Collections.Generic; + +namespace Rokojori +{ + public class GDSceneSFO:SceneFileObject + { + public static readonly string headerType = "gd_scene"; + + public readonly SFHNumberAttribute loadSteps = new SFHNumberAttribute( "loadSteps" ); + public readonly SFHNumberAttribute format = new SFHNumberAttribute( "format" ); + public readonly SFHStringAttribute uid = new SFHStringAttribute( "uid" ); + + } +} \ No newline at end of file diff --git a/Runtime/Godot/Scenes/Objects/NodeSFO.cs b/Runtime/Godot/Scenes/Objects/NodeSFO.cs new file mode 100644 index 0000000..71b8c21 --- /dev/null +++ b/Runtime/Godot/Scenes/Objects/NodeSFO.cs @@ -0,0 +1,60 @@ +using Godot; +using System.Collections.Generic; + +namespace Rokojori +{ + public class NodeSFO:SceneFileObject + { + public static readonly string headerType = "node"; + + + protected NodeSFO _parentObject; + public readonly SFHStringAttribute name = new SFHStringAttribute( "name" ); + public readonly SFHStringAttribute parent = new SFHStringAttribute( "parent" ); + public readonly SFHStringAttribute type = new SFHStringAttribute( "type" ); + protected string _elementPath; + public string elementPath => _elementPath; + + + protected override void _CreateFromHeaderEntry( SceneFileEntry headerEntry ) + { + _elementPath = name.value; + var parentPath = parent.value; + + if ( parentPath == null ) + { + _elementPath = "."; + } + else + { + if ( parentPath != "." ) + { + _elementPath = parentPath + "/" + name.value; + } + } + } + + public void ResolveParent( Dictionary nodeMap, NodeSFO rootNode ) + { + var parentPath = parent.value; + + if ( parentPath == "." ) + { + _parentObject = rootNode; + return; + } + + if ( parentPath != "." && parentPath != null ) + { + if ( nodeMap.ContainsKey( parentPath ) ) + { + _parentObject = nodeMap[ parentPath ]; + } + else + { + RJLog.Log( "Parent not found:", name.value, parentPath, elementPath ); + } + } + } + } +} \ No newline at end of file diff --git a/Runtime/Godot/Scenes/Objects/SceneFileObject.cs b/Runtime/Godot/Scenes/Objects/SceneFileObject.cs new file mode 100644 index 0000000..c2f2ff6 --- /dev/null +++ b/Runtime/Godot/Scenes/Objects/SceneFileObject.cs @@ -0,0 +1,38 @@ +using Godot; +using System.Collections.Generic; + +namespace Rokojori +{ + public class SceneFileObject + { + protected SceneFileEntry _headerEntry; + protected List _attributes = new List(); + + public SceneFileObject() + { + SetAttributes(); + } + + public void CreateFromHeaderEntry( SceneFileEntry headerEntry ) + { + ReadAttributes( headerEntry.header ); + + _CreateFromHeaderEntry( headerEntry); + } + + protected virtual void _CreateFromHeaderEntry( SceneFileEntry headerEntry ) + { + + } + + protected virtual void SetAttributes() + { + _attributes = ReflectionHelper.GetFieldsOfType( this ); + } + + protected void ReadAttributes( SceneFileHeader entry ) + { + _attributes.ForEach( a => a.GetValue( entry ) ); + } + } +} \ No newline at end of file diff --git a/Runtime/Godot/Scenes/Objects/SceneFileObjectFactory.cs b/Runtime/Godot/Scenes/Objects/SceneFileObjectFactory.cs new file mode 100644 index 0000000..c356739 --- /dev/null +++ b/Runtime/Godot/Scenes/Objects/SceneFileObjectFactory.cs @@ -0,0 +1,42 @@ +using Godot; +using System.Collections.Generic; +using System; + +namespace Rokojori +{ + public class SceneFileObjectFactory + { + static Dictionary> factory = + new Dictionary>() + { + { GDSceneSFO.headerType, () => new GDSceneSFO() }, + { ExtResourceSFO.headerType, () => new ExtResourceSFO() }, + { SubResourceSFO.headerType, () => new SubResourceSFO() }, + { NodeSFO.headerType, () => new NodeSFO() }, + { EditableSFO.headerType, () => new EditableSFO() } + }; + + public static SceneFileObject Create( SceneFileEntry entry ) + { + if ( entry.header == null ) + { + RJLog.Log( "Has no header >>", entry.line ); + return null; + } + + foreach ( var vk in factory ) + { + if ( entry.IsHeaderType( vk.Key ) ) + { + var sfo = vk.Value(); + sfo.CreateFromHeaderEntry( entry ); + + return sfo; + } + } + + RJLog.Log( "Unknown header type >> ", entry.header.type, ":", entry.line ); + return null; + } + } +} \ No newline at end of file diff --git a/Runtime/Godot/Scenes/Objects/SubResourceSFO.cs b/Runtime/Godot/Scenes/Objects/SubResourceSFO.cs new file mode 100644 index 0000000..07f837c --- /dev/null +++ b/Runtime/Godot/Scenes/Objects/SubResourceSFO.cs @@ -0,0 +1,13 @@ +using Godot; +using System.Collections.Generic; + +namespace Rokojori +{ + public class SubResourceSFO:SceneFileObject + { + public static readonly string headerType = "sub_resource"; + + public readonly SFHStringAttribute id = new SFHStringAttribute( "uid" ); + public readonly SFHStringAttribute type = new SFHStringAttribute( "type" ); + } +} \ No newline at end of file diff --git a/Runtime/Godot/Scenes/Parsers/SceneFileHeaderParser.cs b/Runtime/Godot/Scenes/Parsers/SceneFileHeaderParser.cs new file mode 100644 index 0000000..b2e0062 --- /dev/null +++ b/Runtime/Godot/Scenes/Parsers/SceneFileHeaderParser.cs @@ -0,0 +1,147 @@ +using Godot; +using System.Collections.Generic; + +namespace Rokojori +{ + public class SceneFileHeaderParser + { + public bool hasError = true; + public string errorMessage; + string line; + + public void Parse( SceneFileEntry entry ) + { + + line = entry.line.Substring( 1, entry.line.Length - 2 ); + + var lexer = new SceneFileLexer(); + var tokens = lexer.LexToList( line ); + + if ( lexer.hasError || tokens.Count == 0) + { + errorMessage = "Lexing failed"; + return; + } + + lexer.GrabMatches( tokens, line ); + + /*var info = ""; + tokens.ForEach( + ( tk )=> + { + info += tk.type + " " + tk.match + "\n"; + } + );*/ + + var offset = 0; + var token = tokens[ 0 ]; + + if ( ! token.Is( LexerMatcherLibrary.CwordMatcher ) ) + { + errorMessage = "First token is not a word: "+ token.type + " '" + token.match + "'"; + return; + } + + var header = new SceneFileHeader(); + + entry.header = header; + + header.type = tokens[ 0 ].match; + + offset = 1; token = tokens[ offset ]; + + while ( offset < tokens.Count && token != null ) + { + if ( ! token.Is( LexerMatcherLibrary.WhiteSpaceMatcher ) ) + { + errorMessage = "Expected white space, but got: " + token.type + " '" + token.match + "'"; + return; + } + + offset ++; token = tokens[ offset ]; + + var assignmentResult = SceneFileLexer.ReadAssignment( tokens, offset ); + + if ( assignmentResult.hasError ) + { + errorMessage = assignmentResult.errorMessage; + return; + } + + header.attributes.Add( assignmentResult.GetNamedValue() ); + + offset = assignmentResult.endOffset; + token = tokens[ offset ]; + + + /*if ( ! token.Is( LexerMatcherLibrary.CwordMatcher ) ) + { + errorMessage = "Expected word for attribute name, but got: " + token.type + " '" + token.match + "'"; + return; + } + + var attribute = new SceneFileNamedValue(); + attribute.name = token.match; + + offset ++; token = tokens[ offset ]; + + if ( ! token.Is( LexerMatcherLibrary.OperatorMatcher, "=" ) ) + { + errorMessage = "Expected assignment operator, but got:" + token.type + " '" + token.match + "'"; + return; + } + + offset ++; token = tokens[ offset ]; + + if ( token.Is( LexerMatcherLibrary.NumberMatcher ) ) + { + attribute.value = token.match; + header.attributes.Add( attribute ); + + } + else if ( token.Is( LexerMatcherLibrary.DoubleQuotedStringMatcher ) ) + { + attribute.value = token.match; + header.attributes.Add( attribute ); + } + else if ( token.Is( LexerMatcherLibrary.CFunctionMatcher ) ) + { + var closer = LexerEvent.FindClosingBracket( tokens, offset + 1 ); + + if ( closer.type != LexerEvent.FindResultType.Found ) + { + errorMessage = "Function with unmatched brackets: " + token.type + " '" + token.match + "'"; + return; + } + + for ( int i = offset; i <= closer.index; i++ ) + { + attribute.value += tokens[ i ].match; + } + + offset = closer.index + 1; + + } + else + { + errorMessage = "Unexpected token:" + token.type + " '" + token.match + "'"; + return; + } + + */ + + offset ++; + + token = ( offset < tokens.Count - 1 ) ? tokens[ offset ] : null ; + + + } + + + //RJLog.Log( info ); + + hasError = false; + } + } + +} \ No newline at end of file diff --git a/Runtime/Godot/Scenes/Parsers/SceneFileLexer.cs b/Runtime/Godot/Scenes/Parsers/SceneFileLexer.cs new file mode 100644 index 0000000..2a0fcb4 --- /dev/null +++ b/Runtime/Godot/Scenes/Parsers/SceneFileLexer.cs @@ -0,0 +1,129 @@ +using Godot; +using System.Collections.Generic; + +namespace Rokojori +{ + public class SceneFileLexer:Lexer + { + public SceneFileLexer() + { + AddAllMatchers( + LexerMatcherLibrary.DoubleQuotedStringMatcher, + LexerMatcherLibrary.NumberMatcher, + LexerMatcherLibrary.NullMatcher, + LexerMatcherLibrary.BoolMatcher, + LexerMatcherLibrary.BreakMatcher, + LexerMatcherLibrary.WhiteSpaceMatcher, + LexerMatcherLibrary.BracketMatcher, + LexerMatcherLibrary.OperatorMatcher, + LexerMatcherLibrary.CFunctionMatcher, + LexerMatcherLibrary.CwordMatcher, + LexerMatcherLibrary.AnySymbolMatcher + ); + } + + public class SceneFileAssignmentResult + { + public bool hasError = true; + public string errorMessage = ""; + + public string name; + public string value; + + public SceneFileNamedValue GetNamedValue() + { + return SceneFileNamedValue.Create( name, value ); + } + + public int startOffset; + public int endOffset; + + public static SceneFileAssignmentResult Create( int startOffset ) + { + var r = new SceneFileAssignmentResult(); + + r.startOffset = startOffset; + + return r; + } + + public SceneFileAssignmentResult AsError( int offset, LexerEvent le, string message ) + { + hasError = true; + endOffset = offset; + errorMessage = message + " Token: " + le.type + "'" + le.match + "'" ; + + return this; + } + + public SceneFileAssignmentResult AsValue( int offset, LexerEvent le ) + { + hasError = false; + endOffset = offset; + value = le.match; + + return this; + } + + public SceneFileAssignmentResult AsValue( int offset, string value ) + { + hasError = false; + endOffset = offset; + this.value = value; + + return this; + } + + } + + public static SceneFileAssignmentResult ReadAssignment( List tokens, int offset ) + { + var result = SceneFileAssignmentResult.Create( offset ); + var token = tokens[ offset ]; + + if ( ! token.Is( LexerMatcherLibrary.CwordMatcher ) ) + { + return result.AsError( offset, token, "Expected a word as name." ) ; + } + + result.name = token.match; + + offset ++; token = tokens[ offset ]; + + if ( ! token.Is( LexerMatcherLibrary.OperatorMatcher, "=" ) ) + { + return result.AsError( offset, token, "Expected assignment operator." ) ; + } + + offset ++; token = tokens[ offset ]; + + if ( token.Is( LexerMatcherLibrary.NumberMatcher ) || + token.Is( LexerMatcherLibrary.DoubleQuotedStringMatcher ) + ) + { + return result.AsValue( offset, token ); + } + else if ( token.Is( LexerMatcherLibrary.CFunctionMatcher ) ) + { + var start = offset + 1; + var closer = LexerEvent.FindClosingBracket( tokens, offset + 1 ); + + if ( closer.type != LexerEvent.FindResultType.Found ) + { + return result.AsError( offset, token, "Unmatched brackets."); + } + + var length = ( closer.index - start ) + 1; + + var value = LexerEvent.GetMatchFromRange( tokens, start, length ); + + return result.AsValue( closer.index, value ); + + } + else + { + return result.AsError( offset, token, "Unexpected token."); + } + } + } +} \ No newline at end of file diff --git a/Runtime/Godot/Scenes/Parsers/SceneFileLinesLexer.cs b/Runtime/Godot/Scenes/Parsers/SceneFileLinesLexer.cs new file mode 100644 index 0000000..e1daba9 --- /dev/null +++ b/Runtime/Godot/Scenes/Parsers/SceneFileLinesLexer.cs @@ -0,0 +1,66 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class GodotTextSceneLexer:Lexer + { + public static List Lex( string source ) + { + var lexer = new GodotTextSceneLexer(); + var events = lexer.LexToList( source ); + + if ( lexer.hasError ) + { + return null; + } + + events.ForEach( ev => { ev.GrabMatch( source ); } ); + + + return events; + } + + public static readonly LexerMatcher HeaderMatcher = LexerMatcher.CreateExtended( + "Header", "\\[.+\\]" + ); + + public static readonly LexerMatcher SeperatorMatcher = LexerMatcher.CreateExtended( + "Seperator", "\\l\\l" + ); + + // ((?:\w|_|\/)+) = .+?(\r\n|\r|\n) + // ((?:\\w|_|\\/)+) = .+?(\\r\\n|\\r|\\n) + public static readonly LexerMatcher MemberMatcher = LexerMatcher.CreateExtended( + "Member", "(?:\\w|_|\\/)+ = .+?" + ); + + // ".+?"\: .+?,(\r\n|\r|\n) + // \".+?\"\\: .+?,\\l + public static readonly LexerMatcher SubMemberMatcher = LexerMatcher.CreateExtended( + "SubMember", "\".+?\"\\: .+?" + ); + + public static readonly LexerMatcher SubMemberStartMatcher = LexerMatcher.CreateExtended( + "SubMemberStart", ".+\\[\\{" + ); + + public static readonly LexerMatcher SubMemberEndMatcher = LexerMatcher.CreateExtended( + "SubMemberEnd", "\\}\\]" + ); + + public GodotTextSceneLexer() + { + AddAllMatchers( + GodotTextSceneLexer.HeaderMatcher, + GodotTextSceneLexer.SeperatorMatcher, + GodotTextSceneLexer.SubMemberStartMatcher, + GodotTextSceneLexer.SubMemberEndMatcher, + GodotTextSceneLexer.SubMemberMatcher, + GodotTextSceneLexer.MemberMatcher, + LexerMatcherLibrary.AnySymbolMatcher + ); + } + } +} \ No newline at end of file diff --git a/Runtime/Godot/Scenes/Parsers/SceneFileParser.cs b/Runtime/Godot/Scenes/Parsers/SceneFileParser.cs new file mode 100644 index 0000000..1b5dc4a --- /dev/null +++ b/Runtime/Godot/Scenes/Parsers/SceneFileParser.cs @@ -0,0 +1,46 @@ +using Godot; +using System.Collections.Generic; + +namespace Rokojori +{ + public class SceneFileParser + { + public string source; + public SceneFile sceneFile; + + public void Parse( string source ) + { + this.source = source; + + var lines = RegexUtility.SplitLines( source ); + var lexer = new GodotTextSceneLexer(); + + var lineIndex = 1; + + sceneFile = new SceneFile(); + + lines.ForEach( + ( ln )=> + { + var matcher = lexer.GetMatcher( ln ); + + if ( matcher == null ) + { + sceneFile.entries.Add( SceneFile.Seperator() ); + } + else + { + var linePreview = ln; + sceneFile.entries.Add( SceneFile.Create( matcher.type, ln ) ); + } + + + lineIndex ++; + } + ); + + sceneFile.CreateObjects(); + + } + } +} \ No newline at end of file diff --git a/Runtime/Godot/Scenes/SceneFile.cs b/Runtime/Godot/Scenes/SceneFile.cs new file mode 100644 index 0000000..98f321c --- /dev/null +++ b/Runtime/Godot/Scenes/SceneFile.cs @@ -0,0 +1,98 @@ +using Godot; +using System.Collections.Generic; + +namespace Rokojori +{ + public class SceneFile + { + public static SceneFileEntry Create( string type, string line ) + { + var entry = new SceneFileEntry(); + entry.type = type; + entry.line = line; + + entry.Parse(); + return entry; + } + + public static SceneFileEntry Seperator() + { + return Create( GodotTextSceneLexer.SeperatorMatcher.type, "" ); + } + + public List entries = new List(); + public List objects = new List(); + + public GDSceneSFO gdScene; + public NodeSFO rootNode; + public List nodes = new List(); + public Dictionary nodeMap = new Dictionary(); + public List extResources = new List(); + public List subResourceSFOs = new List(); + + public SceneFile GetSerializableJSONVersion() + { + var file = new SceneFile(); + file.entries = entries; + + return file; + } + + public void CreateObjects() + { + entries.ForEach( + ( e )=> + { + if ( ! e.hasHeader ) + { + return; + } + + var sfo = SceneFileObjectFactory.Create( e ); + + if ( sfo == null ) + { + return; + } + + if ( sfo is GDSceneSFO ) + { + gdScene = (GDSceneSFO) sfo; + } + else if ( sfo is NodeSFO ) + { + var node = (NodeSFO) sfo; + nodes.Add( node ); + + if ( node.elementPath == "." ) + { + rootNode = node; + } + + nodeMap[ node.elementPath ] = node; + + // RJLog.Log( "'" + node.elementPath + "'", node.name.value ); + + } + else if ( sfo is ExtResourceSFO ) + { + extResources.Add( (ExtResourceSFO) sfo ); + } + else if ( sfo is SubResourceSFO ) + { + subResourceSFOs.Add( (SubResourceSFO) sfo ); + } + + objects.Add( sfo ); + } + ); + + nodes.ForEach( + ( n )=> + { + n.ResolveParent( nodeMap, rootNode ); + } + ); + } + } +} \ No newline at end of file diff --git a/Runtime/Godot/Scenes/SceneFileEntry.cs b/Runtime/Godot/Scenes/SceneFileEntry.cs new file mode 100644 index 0000000..efa307f --- /dev/null +++ b/Runtime/Godot/Scenes/SceneFileEntry.cs @@ -0,0 +1,38 @@ +using Godot; +using System.Collections.Generic; + +namespace Rokojori +{ + public class SceneFileEntry + { + public string type; + public string line; + public SceneFileHeader header; + public SceneFileNamedValue member; + public SceneFileNamedValue subMember; + public bool hasError = false; + + + public bool hasHeader => header != null; + + public bool IsHeaderType( string type ) + { + return header != null && header.type == type; + } + + public void Parse() + { + if ( type == GodotTextSceneLexer.HeaderMatcher.type ) + { + var headerParser = new SceneFileHeaderParser(); + headerParser.Parse( this ); + + if ( headerParser.hasError ) + { + hasError = true; + } + } + } + } + +} \ No newline at end of file diff --git a/Runtime/Godot/Scenes/SceneFileNamedValue.cs b/Runtime/Godot/Scenes/SceneFileNamedValue.cs new file mode 100644 index 0000000..5212bc5 --- /dev/null +++ b/Runtime/Godot/Scenes/SceneFileNamedValue.cs @@ -0,0 +1,19 @@ +using Godot; +using System.Collections.Generic; + +namespace Rokojori +{ + public class SceneFileNamedValue + { + public string name; + public string value; + + public static SceneFileNamedValue Create( string name, string value ) + { + var nv = new SceneFileNamedValue(); + nv.name = name; + nv.value = value; + return nv; + } + } +} \ No newline at end of file diff --git a/Runtime/Godot/Scenes/SceneFileReader.cs b/Runtime/Godot/Scenes/SceneFileReader.cs new file mode 100644 index 0000000..b257069 --- /dev/null +++ b/Runtime/Godot/Scenes/SceneFileReader.cs @@ -0,0 +1,219 @@ +using Godot; + +using System; +using System.Collections.Generic; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class SceneFileReader:Node + { + [Export] + public string path = ""; + + [Export] + public bool load = false; + + [Export] + public bool exportJSON = false; + + [Export] + public bool exportHTML = false; + + + public override void _Process( double delta ) + { + LoadScene(); + } + + void LoadScene() + { + if ( ! load ) + { + return; + } + + load = false; + + var text = FilesSync.LoadUTF8( path ); + + var parser = new SceneFileParser(); + + parser.Parse( text ); + + if ( exportJSON ) + { + FilesSync.SaveJSON( path + ".json", parser.sceneFile.GetSerializableJSONVersion() ); + } + + if ( exportHTML ) + { + var nodes = parser.sceneFile.nodes; + RJLog.Log( "Nodes:", nodes.Count ); + + var doc = new HtmlDocument(); + var html = doc.documentElement; + var body = html.querySelector( HtmlElementNodeName.body ); + var head = html.querySelector( HtmlElementNodeName.head ); + + head.AddScript( + @" + + function main() + { + let elements = document.querySelectorAll( '.closeable' ); + + for ( let i = 0; i < elements.length; i++ ) + { + let element = elements[ i ]; + + element.addEventListener( 'click', + ( ev )=> + { + + let target = element.getAttribute( 'data-close-target' ); + + let targetElement = element; + + let parentRegex = /^\.\.\s/; + + while ( parentRegex.test( target ) ) + { + target = target.replace( parentRegex, '' ); + targetElement = targetElement.parentElement; + } + + let rootRegex = /^\.\.\.\s/; + + if ( rootRegex.test( target ) ) + { + target = target.replace( rootRegex, '' ); + targetElement = document.documentElement; + } + + let closeTarget = element.parentElement.querySelector( target ); + + console.log( ev, target, closeTarget); + closeTarget.style.display = closeTarget.style.display === 'none' ? 'block' : 'none'; + + element.setAttribute( 'data-close-state', closeTarget.style.display ); + + ev.preventDefault(); + return false; + }, + true + ); + } + } + + window.addEventListener( 'load', () => { main(); }); + + " + ); + + head.AddStyle( + @" + body + { + overflow-x: hidden; + margin: 2em 0em; + font-family: Helvetica, Arial, sans; + background: hsl(0,0%,10%); + color: hsl(0,0%,80%) + } + + .closeable + { + cursor: pointer; + opacity:1; + transition: opacity 300ms ease; + } + + .closeable[data-close-state='none'] + { + opacity: 0.7; + } + + .node + { + display: block; + position: relative; + left: 2em; + } + + gd-name + { + border-radius: 0.5em; + padding: 1em 1em; + background-color: hsl(0,0%,20%); + margin: 0.5em 1em; + display: inline-block; + font-weight: bold; + + } + + gd-type + { + display: inline-block; + opacity: 0.5; + } + + " ); + + + + var elementMap = new Dictionary(); + + var GD_Node = HtmlElementNodeName.CreateNodeName( "gd-node" ); + var GD_Name = HtmlElementNodeName.CreateNodeName( "gd-name" ); + var GD_Type = HtmlElementNodeName.CreateNodeName( "gd-type" ); + var GD_Children = HtmlElementNodeName.CreateNodeName( "gd-children" ); + + for ( int i = 0; i < nodes.Count; i++ ) + { + var node = nodes[ i ]; + + //RJLog.Log( i, node.name.value ); + + var attachmentElement = body; + + var elementPath = node.elementPath; + + var parent = node.parent.value; + + if ( parent != null && elementMap.ContainsKey( parent ) ) + { + attachmentElement = elementMap[ parent ].querySelector( GD_Children ); + } + + var nodeElement = doc.Create( GD_Node ); + + nodeElement.SetAttribute( "class", "node" ); + + var nameElement = nodeElement.AddElement( GD_Name, node.name.value ); + nameElement.SetAttribute( "class", "closeable" ); + nameElement.SetAttribute( "data-close-target", ".. " + GD_Children.selector ); + + nodeElement.AddElement( GD_Type, node.type.value ); + + nodeElement.AddElement( GD_Children ); + + + + elementMap[ elementPath ] = nodeElement; + + // RJLog.Log( "'" + elementPath + "'", node.name.value ); + + attachmentElement.AppendChild( nodeElement ); + + + } + + + FilesSync.SaveUTF8( path + ".html", new HtmlSerializer().Serialize( doc.documentElement ) ); + } + + } + } +} \ No newline at end of file diff --git a/Runtime/Graphs/Trees/TreeWalker.cs b/Runtime/Graphs/Trees/TreeWalker.cs index 7b5a8c3..5f2a5b7 100644 --- a/Runtime/Graphs/Trees/TreeWalker.cs +++ b/Runtime/Graphs/Trees/TreeWalker.cs @@ -1,6 +1,6 @@ using System.Collections; using System.Collections.Generic; - +using System; namespace Rokojori { @@ -220,7 +220,7 @@ namespace Rokojori if ( its > max ) { - throw new System.Exception(); + throw new Exception(); } node = LastChild( node ); @@ -240,7 +240,76 @@ namespace Rokojori return NextNonChild( node ); } - public void Iterate( N node, System.Action callback, bool childrenOnly = false ) + public int GetDepth( N node, Dictionary depthMap = null ) + { + if ( depthMap == null ) + { + var depth = 0; + var it = Parent( node ); + var maxDepth = 1000; + + while ( it != null ) + { + depth ++; + + if ( depth == maxDepth ) + { + RJLog.Log( "reached max depth", it ); + return depth; + } + + it = Parent( it ); + } + + return depth; + } + + if ( depthMap.ContainsKey( node ) ) + { + return depthMap[ node ]; + } + + var parent = Parent( node ); + + if ( parent == null ) + { + depthMap[ node ] = 0; + return 0; + } + + if ( depthMap.ContainsKey( parent ) ) + { + var parentDepth = depthMap[ parent ]; + var nodeDepth = parentDepth + 1; + + depthMap[ node ] = nodeDepth; + + return nodeDepth; + } + + + depthMap[ node ] = GetDepth( node ); + + return depthMap[ node ]; + } + + public void DepthIterate( N node, Action callback, bool childrenOnly = false, Dictionary depthMap = null ) + { + if ( depthMap == null ) + { + depthMap = new Dictionary(); + } + + var iterationCallback = ( N node )=> + { + var depth = GetDepth( node, depthMap ); + callback( node, depth ); + }; + + Iterate( node, iterationCallback, childrenOnly ); + } + + public void Iterate( N node, Action callback, bool childrenOnly = false ) { var end = IterationEndOf( node ); var it = node; @@ -257,7 +326,7 @@ namespace Rokojori } } - public N Find( N node, System.Predicate predicate, bool childrenOnly ) + public N Find( N node, Predicate predicate, bool childrenOnly ) { var end = IterationEndOf( node ); var it = node; @@ -280,9 +349,9 @@ namespace Rokojori return null; } - public void Filter( List list, N node, System.Predicate predicate, bool childrenOnly ) + public void Filter( List list, N node, Predicate predicate, bool childrenOnly ) { - System.Action addToList = ( N n ) => + Action addToList = ( N n ) => { if ( predicate( n ) ) { @@ -293,9 +362,9 @@ namespace Rokojori Iterate( node, addToList, childrenOnly ); } - public void FilterAndMap( List list, N node, System.Predicate predicate, System.Func mapper, bool childrenOnly ) + public void FilterAndMap( List list, N node, Predicate predicate, Func mapper, bool childrenOnly ) { - System.Action addToList = ( N n ) => + Action addToList = ( N n ) => { if ( predicate( n ) ) { @@ -306,9 +375,9 @@ namespace Rokojori Iterate( node, addToList, childrenOnly ); } - public void Map( List list, N node, System.Predicate predicate, System.Func mapper, bool childrenOnly ) + public void Map( List list, N node, Predicate predicate, Func mapper, bool childrenOnly ) { - System.Action addToList = ( N n ) => + Action addToList = ( N n ) => { list.Add( mapper( n ) ); }; diff --git a/Runtime/Html/HtmlAttributeNode.cs b/Runtime/Html/HtmlAttributeNode.cs new file mode 100644 index 0000000..162a727 --- /dev/null +++ b/Runtime/Html/HtmlAttributeNode.cs @@ -0,0 +1,25 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class HtmlAttributeNode:HtmlNode + { + public HtmlAttributeNode( HtmlDocument document, HtmlElementNode parent, string name, string value ):base( document, HtmlNode.NodeType.Attribute ) + { + _name = name; + _value = value; + _parent = parent; + } + + HtmlElementNode _parent; + string _name; + string _value; + + public string name => _name; + public string value => _value; + + + } +} \ No newline at end of file diff --git a/Runtime/Html/HtmlDocument.cs b/Runtime/Html/HtmlDocument.cs new file mode 100644 index 0000000..256a6d6 --- /dev/null +++ b/Runtime/Html/HtmlDocument.cs @@ -0,0 +1,41 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class HtmlDocument + { + HtmlElementNode _documentElement; + public HtmlElementNode documentElement => _documentElement; + + public HtmlDocument( bool addHeadAndBody = true ) + { + _documentElement = HtmlElementNodeName.html.Create( this ); + + if ( addHeadAndBody ) + { + _documentElement.AppendChild( HtmlElementNodeName.head.Create( this ) ); + _documentElement.AppendChild( HtmlElementNodeName.body.Create( this ) ); + } + } + + public HtmlElementNode Create( HtmlElementNodeName nodeName, string text = null ) + { + var element = nodeName.Create( this ); + + if ( text != null ) + { + element.AddText( text ); + } + + return element; + } + + public HtmlTextNode CreateText( string text ) + { + return new HtmlTextNode( this, text ); + } + + } +} \ No newline at end of file diff --git a/Runtime/Html/HtmlElementNode.cs b/Runtime/Html/HtmlElementNode.cs new file mode 100644 index 0000000..067c77e --- /dev/null +++ b/Runtime/Html/HtmlElementNode.cs @@ -0,0 +1,140 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class HtmlElementNode:HtmlNode + { + public HtmlElementNode( HtmlDocument document, string nodeName ):base( document, HtmlNode.NodeType.Element ) + { + _nodeName = nodeName; + } + + string _nodeName; + public string nodeName => _nodeName; + + List _children = new List(); + public int numChildren => _children.Count; + + public HtmlNode GetChildAt( int index ) + { + return _children[ index ]; + } + + public bool HasOnlyTextNodes() + { + for ( int i = 0; i < _children.Count; i++ ) + { + if ( _children[ i ].nodeType != NodeType.Text ) + { + return false; + } + } + + return _children.Count > 0; + } + + public void RemoveChild( HtmlNode node ) + { + var childIndex = _children.IndexOf( node ); + + if ( childIndex == -1 ) + { + return; + } + + node._SetParent( null ); + _children.RemoveAt( childIndex ); + } + + public void AppendChild( HtmlNode node ) + { + if ( node.parentNode == this ) + { + return; + } + + if ( node.parentNode != null ) + { + var element = node.parentNode as HtmlElementNode; + element.RemoveChild( node ); + } + + _children.Add( node ); + + node._SetParent( this ); + + } + + public HtmlTextNode AddText( string text ) + { + var textNode = document.CreateText( text ); + AppendChild( textNode ); + + return textNode; + } + + public HtmlElementNode AddScript( string script ) + { + return AddElement( HtmlElementNodeName.script, script ); + } + + public HtmlElementNode AddStyle( string style ) + { + return AddElement( HtmlElementNodeName.style, style ); + } + + public HtmlElementNode AddElement( HtmlElementNodeName name, string text = null ) + { + var en = document.Create( name, text ); + AppendChild( en ); + return en; + } + + public void SetAttribute( string name, string value ) + { + var att = new HtmlAttributeNode( document, this, name, value ); + _attributes.Add( att ); + } + + + List _attributes = new List(); + + public int numAttributes => _attributes.Count; + + public HtmlAttributeNode GetAttributeAt( int index ) + { + return _attributes[ index ]; + } + + public HtmlElementNode querySelector( string selector ) + { + var nodeName = HtmlElementNodeName.CreateNodeName( selector ); + + return querySelector( nodeName ); + } + + public HtmlElementNode querySelector( HtmlElementSelector selector ) + { + + var element = HtmlWalker.instance.Find( this, + n => + { + var element = n as HtmlElementNode; + + if ( element == null ) + { + return false; + } + + return selector.Selects( element ); + }, + + false + ); + + return element == null ? null : ( element as HtmlElementNode ); + } + } +} \ No newline at end of file diff --git a/Runtime/Html/HtmlElementNodeName.cs b/Runtime/Html/HtmlElementNodeName.cs new file mode 100644 index 0000000..d9be00d --- /dev/null +++ b/Runtime/Html/HtmlElementNodeName.cs @@ -0,0 +1,41 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class HtmlElementNodeName:HtmlElementSelector + { + public static readonly HtmlElementNodeName html = CreateNodeName( "html" ); + public static readonly HtmlElementNodeName head = CreateNodeName( "head" ); + public static readonly HtmlElementNodeName body = CreateNodeName( "body" ); + public static readonly HtmlElementNodeName br = CreateNodeName( "br" ); + public static readonly HtmlElementNodeName a = CreateNodeName( "a" ); + public static readonly HtmlElementNodeName style = CreateNodeName( "style" ); + public static readonly HtmlElementNodeName script = CreateNodeName( "script" ); + + string _nodeName; + + public string selector => _nodeName; + + public static HtmlElementNodeName CreateNodeName( string type ) + { + var elementNodeType = new HtmlElementNodeName(); + elementNodeType._nodeName = type; + + return elementNodeType; + } + + public HtmlElementNode Create( HtmlDocument document ) + { + return new HtmlElementNode( document, _nodeName ); + } + + public bool Selects( HtmlElementNode elementNode ) + { + return elementNode.nodeName == _nodeName; + } + + + } +} \ No newline at end of file diff --git a/Runtime/Html/HtmlElementSelector.cs b/Runtime/Html/HtmlElementSelector.cs new file mode 100644 index 0000000..d4127b7 --- /dev/null +++ b/Runtime/Html/HtmlElementSelector.cs @@ -0,0 +1,14 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Text; + +namespace Rokojori +{ + public interface HtmlElementSelector + { + bool Selects( HtmlElementNode elementNode ); + + string selector { get; } + } +} \ No newline at end of file diff --git a/Runtime/Html/HtmlNode.cs b/Runtime/Html/HtmlNode.cs new file mode 100644 index 0000000..2d51a5c --- /dev/null +++ b/Runtime/Html/HtmlNode.cs @@ -0,0 +1,46 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public abstract class HtmlNode + { + public enum NodeType + { + Element = 1, + Attribute = 2, + Text = 3, + CDataSection = 4, + ProcessingInstruction = 7, + Comment = 8, + Document = 9, + DocumentType = 10, + DocumentFragment = 11 + } + + NodeType _nodeType; + public NodeType nodeType => _nodeType; + + HtmlDocument _document; + public HtmlDocument document => _document; + + protected HtmlNode _parentNode; + public HtmlNode parentNode => _parentNode; + public HtmlElementNode parentElement => _parentNode as HtmlElementNode; + public void _SetParent( HtmlNode node ) + { + _parentNode = node; + } + + public virtual string nodeValue => null; + public virtual string textContent => ""; + + public HtmlNode( HtmlDocument document, NodeType type ) + { + _document = document; + _nodeType = type; + } + + } +} \ No newline at end of file diff --git a/Runtime/Html/HtmlSerializer.cs b/Runtime/Html/HtmlSerializer.cs new file mode 100644 index 0000000..c70dfa1 --- /dev/null +++ b/Runtime/Html/HtmlSerializer.cs @@ -0,0 +1,174 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Text; + +namespace Rokojori +{ + public class HtmlSerializer + { + + StringBuilder sb = new StringBuilder(); + HtmlWalker walker = new HtmlWalker(); + string indent = " "; + Dictionary depthMap = new Dictionary(); + Dictionary indentMap = new Dictionary(); + List stack = new List(); + + + public static string Escape( string rawText ) + { + rawText = RegexUtility.Replace( rawText, "&", "&" ); + rawText = RegexUtility.Replace( rawText, "<", "<" ); + rawText = RegexUtility.Replace( rawText, ">", ">" ); + rawText = RegexUtility.Replace( rawText, "\'", "'" ); + rawText = RegexUtility.Replace( rawText, "\"", """ ); + + return rawText; + } + + public string Serialize( HtmlNode node ) + { + + sb.Append( "" ); + walker.DepthIterate( node, + + ( n, d ) => + { + var depth = GetDepth( n ); + ClosePreviousElements( depth ); + + var element = n as HtmlElementNode; + + if ( element != null ) + { + sb.Append( "\n" ); + sb.Append( GetIndent( depth ) ); + + sb.Append( "<" + element.nodeName ); + + for ( int i = 0; i < element.numAttributes; i++ ) + { + var attribute = element.GetAttributeAt( i ); + + sb.Append( " " ); + sb.Append( attribute.name ); + sb.Append( "=\"" ); + sb.Append( Escape( attribute.value ) ); + sb.Append( "\""); + } + + sb.Append( ">" ); + + stack.Add( element ); + + } + else + { + if ( + HtmlElementNodeName.style.Selects( n.parentElement ) || + HtmlElementNodeName.script.Selects( n.parentElement ) + ) + { + sb.Append( n.nodeValue ); + } + else + { + sb.Append( Escape( n.nodeValue ) ); + } + } + + + }, + + false, + depthMap + ); + + ClosePreviousElements( -1 ); + + return sb.ToString(); + + } + + string GetIndent( int depth ) + { + + if ( indentMap.ContainsKey( depth ) ) + { + return indentMap[ depth ]; + } + + if ( depth == 0 ) + { + indentMap[ 0 ] = ""; + return ""; + } + + if ( indentMap.ContainsKey( depth - 1 ) ) + { + var smallerIndent = indentMap[ depth -1 ]; + indentMap[ depth ] = smallerIndent + indent; + return indentMap[ depth ]; + } + + var sb = new StringBuilder(); + + for ( int i = 0; i < depth; i++ ) + { + sb.Append( indent ); + } + + indentMap[ depth ] = sb.ToString(); + return indentMap[ depth ]; + + } + + int GetDepth( HtmlNode n ) + { + return walker.GetDepth( n, depthMap ); + } + + void ClosePreviousElements( int currentDepth ) + { + for ( int i = stack.Count - 1; i >= 0; i-- ) + { + var stackDepth = GetDepth( stack[ i ] ); + + if ( stackDepth >= currentDepth ) + { + var element = stack[ i ]; + stack.RemoveAt( i ); + + if ( NeedsLineBreak( element ) ) + { + sb.Append( "\n" ); + sb.Append( GetIndent( stackDepth ) ); + } + + sb.Append( "" ); + } + else + { + i = -1; + } + } + } + + bool NeedsLineBreak( HtmlElementNode elementNode ) + { + if ( elementNode.numChildren == 0 || + + elementNode.HasOnlyTextNodes() && + ! ( HtmlElementNodeName.script.Selects( elementNode ) || + HtmlElementNodeName.style.Selects( elementNode ) + ) + ) + { + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/Runtime/Html/HtmlTextNode.cs b/Runtime/Html/HtmlTextNode.cs new file mode 100644 index 0000000..3028c49 --- /dev/null +++ b/Runtime/Html/HtmlTextNode.cs @@ -0,0 +1,18 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class HtmlTextNode:HtmlNode + { + public HtmlTextNode( HtmlDocument document, string textContent ):base( document, HtmlNode.NodeType.Text ) + { + _textContent = textContent; + } + + string _textContent; + + public override string nodeValue => _textContent; + } +} \ No newline at end of file diff --git a/Runtime/Html/HtmlWalker.cs b/Runtime/Html/HtmlWalker.cs new file mode 100644 index 0000000..85d59d7 --- /dev/null +++ b/Runtime/Html/HtmlWalker.cs @@ -0,0 +1,42 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class HtmlWalker: TreeWalker + { + private static HtmlWalker _instance = new HtmlWalker(); + public static HtmlWalker instance => _instance; + + public override int NumChildren( HtmlNode node ) + { + var elementNode = node as HtmlElementNode; + + if ( elementNode != null ) + { + return elementNode.numChildren; + } + + return 0; + } + + public override HtmlNode ChildAt( HtmlNode node, int index ) + { + var elementNode = node as HtmlElementNode; + + if ( elementNode != null ) + { + return elementNode.GetChildAt( index ); + } + + return null; + } + + public override HtmlNode Parent( HtmlNode node ) + { + return node.parentNode; + } + } + +} \ No newline at end of file diff --git a/Runtime/Math/Math3D.cs b/Runtime/Math/Math3D.cs index 5c91340..7cd3094 100644 --- a/Runtime/Math/Math3D.cs +++ b/Runtime/Math/Math3D.cs @@ -35,6 +35,24 @@ namespace Rokojori { return node.GlobalBasis.X; } + + public static Vector3 GetYPlaneForward( Node3D node ) + { + var forward = GetGlobalForward( node ); + + forward.Y = 0; + + return forward.Normalized(); + } + + public static Vector3 GetYPlaneRight( Node3D node ) + { + var right = GetGlobalRight( node ); + + right.Y = 0; + + return right.Normalized(); + } } } \ No newline at end of file diff --git a/Runtime/Math/MathX.cs b/Runtime/Math/MathX.cs index 2f54639..941176d 100644 --- a/Runtime/Math/MathX.cs +++ b/Runtime/Math/MathX.cs @@ -225,5 +225,15 @@ namespace Rokojori } + public static float TimeLerp( float from, float to, float t, float timeDelta ) + { + return Mathf.Lerp( from, to, 1f - Mathf.Pow( t, timeDelta ) ); + } + + public static float PolarAxis( bool negative, bool positive ) + { + return ( negative ? -1 : 0 ) + ( positive ? 1 : 0 ); + } + } } \ No newline at end of file diff --git a/Runtime/Sensors/CombineSensor.cs b/Runtime/Sensors/CombineSensor.cs new file mode 100644 index 0000000..baa1d77 --- /dev/null +++ b/Runtime/Sensors/CombineSensor.cs @@ -0,0 +1,55 @@ + +using Godot; + + +namespace Rokojori +{ + [GlobalClass,Icon("res://Scripts/Rokojori/Rokojori-Action-Library/Icons/RJSensor.svg")] + public partial class CombineSensor : RJSensor + { + [Export] + public RJSensor[] sensors; + + [Export] + public float axisActivationTreshold = 0.75f; + + float _value = 0; + bool _wasActive = false; + bool _isActive = false; + + public override float GetValue() + { + return _value; + } + + public override bool IsActive() + { + return _isActive; + } + + public override bool WasActive() + { + return _wasActive; + } + + public override void UpdateValue( float value ) + { + _value = value; + + _wasActive = _isActive; + _isActive = _value > axisActivationTreshold; + } + + public override void _Process( double delta ) + { + var value = 0f; + + for ( int i = 0; i < sensors.Length; i++ ) + { + value = Mathf.Max( value, sensors[ i ].GetValue() ); + } + + UpdateValue( value ); + } + } +} \ No newline at end of file diff --git a/Runtime/Sensors/Sensors.cs b/Runtime/Sensors/Sensors.cs new file mode 100644 index 0000000..3883295 --- /dev/null +++ b/Runtime/Sensors/Sensors.cs @@ -0,0 +1,24 @@ + +using Godot; + + +namespace Rokojori +{ + public class Sensors + { + public static bool IsActive( RJSensor sensor ) + { + return sensor == null ? false : sensor.IsActive(); + } + + public static float GetValue( RJSensor sensor, float scale = 1 ) + { + return sensor == null ? 0 : sensor.GetValue() * scale; + } + + public static float PolarAxis( RJSensor negative, RJSensor positive ) + { + return GetValue( negative, -1 ) + GetValue( positive, 1 ); + } + } +} \ No newline at end of file diff --git a/Runtime/Shading/Library/Colors.gdshaderinc b/Runtime/Shading/Library/Colors.gdshaderinc new file mode 100644 index 0000000..5e2b533 --- /dev/null +++ b/Runtime/Shading/Library/Colors.gdshaderinc @@ -0,0 +1,142 @@ +#include "res://Scripts/Rokojori/Rokojori-Action-Library/Runtime/Shading/Library/Math.gdshaderinc" + +const float HCV_EPSILON = 1e-10; +const float HSL_EPSILON = 1e-10; + +vec3 RGBtoHCV( vec3 rgb ) +{ + vec4 P = ( rgb.g < rgb.b ) ? vec4( rgb.bg, -1.0, 2.0/3.0 ) : vec4( rgb.gb, 0.0, -1.0/3.0 ); + vec4 Q = ( rgb.r < P.x ) ? vec4( P.xyw, rgb.r ) : vec4( rgb.r, P.yzx ); + float C = Q.x - min( Q.w, Q.y ); + float H = abs( (Q.w - Q.y ) / ( 6.0 * C + HCV_EPSILON ) + Q.z ); + return vec3( H, C, Q.x ); +} + + +vec3 RGBtoHSL( vec3 rgb ) +{ + vec3 HCV = RGBtoHCV( rgb ); + float L = HCV.z - HCV.y * 0.5; + float S = HCV.y / ( 1.0 - abs( L * 2.0 - 1.0 ) + HSL_EPSILON ); + return vec3( HCV.x, S, L ); +} + +vec3 hueToRGB( float hue ) +{ + float R = abs( hue * 6.0 - 3.0 ) - 1.0; + float G = 2.0 - abs( hue * 6.0 - 2.0 ); + float B = 2.0 - abs( hue * 6.0 - 4.0 ); + return clamp( vec3( R,G,B ),0,1 ); +} + +vec3 HSLtoRGB( vec3 hsl ) +{ + vec3 rgb = hueToRGB( hsl.x ); + float C = ( 1.0 - abs( 2.0 * hsl.z - 1.0 )) * hsl.y; + return ( rgb - 0.5 ) * C + hsl.z; +} + +vec3 toLinear( vec3 sRGB ) +{ + return mix( pow( (sRGB + vec3( 0.055 )) * ( 1.0 / ( 1.0 + 0.055 )),vec3( 2.4 )),sRGB * ( 1.0 / 12.92 ),lessThan( sRGB,vec3( 0.04045 )) ); +} + +float modPolarDegrees( float value ) +{ + return mod( value + 180.0, 360.0 ) - 180.0; +} + +float hue360ToYB( float hue ) +{ + return modPolarDegrees( hue - 240.0 ); +} + +float ybToHue360( float yb ) +{ + return mod( yb + 240.0, 360.0 ); +} + +float changeHue360( float hue, float change ) +{ + float yb = hue360ToYB( hue ); + + float absYB = abs( yb ); + + absYB = clamp( absYB + change, 0.0, 180.0 ); + + float realYB = sign( yb ) * absYB; + + return ybToHue360( realYB ); +} + +vec3 shiftHSL360( vec3 hslWithH360, vec3 offset, float blendRadius ) +{ + float distanceToYellow = min( 1.0, abs( hslWithH360.x - 60.0 ) / blendRadius ); + float distanceToBlue = min( 1.0, abs( hslWithH360.x - 240.0 ) / blendRadius ); + + hslWithH360.x = changeHue360( hslWithH360.x, offset.x ); + hslWithH360.y = clamp( hslWithH360.y + offset.y, 0, 1 ); + hslWithH360.z = clamp( hslWithH360.z + offset.z, 0, 1 ); + + return hslWithH360; +} + + +vec3 shiftHSL( vec3 hsl, vec3 offset, float blendRadius ) +{ + hsl.x *= 360.0; + offset.x *= 360.0; + hsl = shiftHSL360( hsl, offset, blendRadius ); + hsl.x /= 360.0; + + return hsl; +} + +vec3 RGBtoHSV( vec3 c ) +{ + vec4 K = vec4( 0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0 ); + vec4 p = mix( vec4( c.bg, K.wz ), vec4( c.gb, K.xy ), step( c.b, c.g )); + vec4 q = mix( vec4( p.xyw, c.r ), vec4( c.r, p.yzx ), step( p.x, c.r )); + + float d = q.x - min( q.w, q.y ); + float e = 1.0e-10; + + return vec3( abs( q.z + ( q.w - q.y ) / ( 6.0 * d + e )), d / ( q.x + e ), q.x ); +} + +vec3 HSVtoRGB( vec3 c ) +{ + vec4 K = vec4( 1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0 ); + vec3 p = abs( fract( c.xxx + K.xyz ) * 6.0 - K.www ); + + return c.z * mix( K.xxx, clamp( p - K.xxx, 0.0, 1.0 ), c.y ); +} + +vec3 mix3( vec3 a, vec3 b, vec3 c, float t ) +{ + float weightA = mapClamped( t, 0, 0.5, 1, 0 ); + float weightB = triangle( t ); + float weightC = mapClamped( t, 0.5, 1, 0, 1 ); + + return a * weightA + b * weightB + c * weightC; +} + +vec4 mix3_v4( vec4 a, vec4 b, vec4 c, float t ) +{ + float weightA = mapClamped( t, 0, 0.5, 1, 0 ); + float weightB = triangle( t ); + float weightC = mapClamped( t, 0.5, 1, 0, 1 ); + + return a * weightA + b * weightB + c * weightC; +} + +vec4 scaleRGB( vec4 rgba, float scale ) +{ + return vec4( rgba.rgb * scale, rgba.a ); +} + +vec4 fade( vec4 rgba, float fade ) +{ + return vec4( rgba.rgb, rgba.a * fade ); +} + diff --git a/Runtime/Shading/Library/Math.gdshaderinc b/Runtime/Shading/Library/Math.gdshaderinc new file mode 100644 index 0000000..3ed6fe2 --- /dev/null +++ b/Runtime/Shading/Library/Math.gdshaderinc @@ -0,0 +1,41 @@ +float clamp01( float value ) +{ + return clamp( value, 0.0, 1.0 ); +} + +vec4 clamp01_v4( vec4 value ) +{ + value.r = clamp01( value.r ); + value.g = clamp01( value.g ); + value.b = clamp01( value.b ); + value.a = clamp01( value.a ); + + return value; +} + +float normalizeToRange( float value, float min, float max ) +{ + return ( value - min ) / ( max - min ); +} + +float normalizeToRange01( float value, float min, float max ) +{ + return clamp01( normalizeToRange( value, min, max ) ); +} + +float map( float value, float inMin, float inMax, float outMin, float outMax ) +{ + return mix( outMin, outMax, normalizeToRange( value, inMin, inMax ) ); +} + +float mapClamped( float value, float inMin, float inMax, float outMin, float outMax ) +{ + return mix( outMin, outMax, normalizeToRange01( value, inMin, inMax ) ); +} + +float triangle( float value ) +{ + float inverted = 1.0 - value; + + return min( value, inverted ) * 2.0; +} \ No newline at end of file diff --git a/Runtime/Shading/Shaders/PostProcessing/ColorCurves.gdshader b/Runtime/Shading/Shaders/PostProcessing/ColorCurves.gdshader new file mode 100644 index 0000000..33dc249 --- /dev/null +++ b/Runtime/Shading/Shaders/PostProcessing/ColorCurves.gdshader @@ -0,0 +1,68 @@ +shader_type canvas_item; + +#include "res://Scripts/Rokojori/Rokojori-Action-Library/Runtime/Shading/Library/Colors.gdshaderinc" +#include "res://Scripts/Rokojori/Rokojori-Action-Library/Runtime/Shading/Library/Math.gdshaderinc" + +uniform sampler2D screenTexture: + hint_screen_texture, + repeat_disable, + filter_linear_mipmap; + +uniform sampler2D rChannelCurve; +uniform float rChannelEffectStrength: hint_range(0,1) = 1; + +uniform sampler2D gChannelCurve; +uniform float gChannelEffectStrength: hint_range(0,1) = 1; + +uniform sampler2D bChannelCurve; +uniform float bChannelEffectStrength: hint_range(0,1) = 1; + +uniform sampler2D hChannelCurve; +uniform float hChannelEffectStrength: hint_range(0,1) = 1; + +uniform sampler2D sChannelCurve; +uniform float sChannelEffectStrength: hint_range(0,1) = 1; + +uniform sampler2D lChannelCurve; +uniform float lChannelEffectStrength: hint_range(0,1) = 1; + +uniform sampler2D lumaTemparatureShiftCurve; +uniform float warmenShift: hint_range(-1,1) = 0; +uniform float saturationShift: hint_range(-1,1) = 0; +uniform float luminanceShift: hint_range(-1,1) = 0; +uniform float lumaTemparatureShiftEffectStrength: hint_range(0,1) = 1; + + +uniform sampler2D lumaSaturationCurve; +uniform float lumaSaturationEffectStrength: hint_range(0,1) = 1; + +uniform float effectStrength : hint_range(0,1) = 1; + +void fragment() +{ + vec4 rgb = texture( screenTexture, UV ); + vec4 originalRGB = rgb; + + rgb.r = mix( rgb.r, texture( rChannelCurve, vec2( rgb.r, 0.0 ) ).x, rChannelEffectStrength ); + rgb.g = mix( rgb.g, texture( gChannelCurve, vec2( rgb.g, 0.0 ) ).x, gChannelEffectStrength ); + rgb.b = mix( rgb.b, texture( bChannelCurve, vec2( rgb.b, 0.0 ) ).x, bChannelEffectStrength ); + + rgb = clamp01_v4( rgb ); + + vec3 hsl = RGBtoHSL( rgb.rgb ); + + hsl.r = mix( hsl.r, hsl.r + texture( hChannelCurve, vec2( hsl.r, 0.0 ) ).x - 0.5, hChannelEffectStrength ); + hsl.r = mod( hsl.r, 1.0 ); + hsl.g = mix( hsl.g, texture( sChannelCurve, vec2( hsl.g, 0.0 ) ).x, sChannelEffectStrength ); + hsl.b = mix( hsl.b, texture( lChannelCurve, vec2( hsl.b, 0.0 ) ).x, lChannelEffectStrength ); + + vec3 lumaTemparatureShift = vec3( warmenShift, -saturationShift, luminanceShift ); + float lumaTemparatureShiftAmount = texture( lumaTemparatureShiftCurve, vec2( hsl.b, 0 ) ).x * 2.0 - 1.0; + lumaTemparatureShiftAmount = lumaTemparatureShiftAmount * lumaTemparatureShiftEffectStrength; + hsl = shiftHSL( hsl, lumaTemparatureShift * lumaTemparatureShiftAmount, 10 ); + + hsl.g = mix( hsl.g, hsl.g + texture( lumaSaturationCurve, vec2( hsl.b, 0.0 ) ).x - 0.5, hChannelEffectStrength ); + rgb = vec4( HSLtoRGB( hsl ), rgb.a ); + + COLOR = mix( originalRGB, rgb, effectStrength ); +} \ No newline at end of file diff --git a/Runtime/Shading/Shaders/PostProcessing/Overlays.gdshader b/Runtime/Shading/Shaders/PostProcessing/Overlays.gdshader new file mode 100644 index 0000000..36e7970 --- /dev/null +++ b/Runtime/Shading/Shaders/PostProcessing/Overlays.gdshader @@ -0,0 +1,30 @@ +shader_type canvas_item; + +#include "res://Scripts/Rokojori/Rokojori-Action-Library/Runtime/Shading/Library/Colors.gdshaderinc" +#include "res://Scripts/Rokojori/Rokojori-Action-Library/Runtime/Shading/Library/Math.gdshaderinc" + +uniform sampler2D screenTexture: + hint_screen_texture, + repeat_disable, + filter_linear_mipmap; + + +uniform sampler2D overlay; +uniform float effectStrength: hint_range(0,1) = 1; + + +void fragment() +{ + vec4 rgb = texture( screenTexture, UV ); + vec4 originalRGB = rgb; + + vec4 overlayColor = texture( overlay, UV ); + vec4 additiveOverlayColor = overlayColor + rgb; + vec4 multipliedOverlayColor = overlayColor * rgb; + + float overlayBlendType = length( overlayColor.rgb ); + vec4 mixed = mix3_v4( multipliedOverlayColor, rgb, additiveOverlayColor, overlayBlendType ); + + rgb = mixed; + COLOR = mix( originalRGB, mixed, effectStrength ); +} \ No newline at end of file diff --git a/Runtime/Text/Lexing/Lexer.cs b/Runtime/Text/Lexing/Lexer.cs index 93906e7..8594b8e 100644 --- a/Runtime/Text/Lexing/Lexer.cs +++ b/Runtime/Text/Lexing/Lexer.cs @@ -42,6 +42,30 @@ namespace Rokojori AddMatcher( type, new Regex( regex ), mode, nextMode ); } + public LexerMatcher GetMatcher( string source, string mode ="" ) + { + var matchers = _modes[ mode ]; + var offset = 0; + + for ( var i = 0; i < matchers.Count; i++ ) + { + var matcher = matchers[ i ]; + var matchLength = matcher.MatchLength( source, offset ); + + if ( matchLength > 0 ) + { + return matcher; + } + } + + return null; + } + + public void GrabMatches( List events, string source ) + { + events.ForEach( ev => { ev.GrabMatch( source ); } ); + } + public void Lex( string source, System.Action callback, int offset = 0, string mode = "" ) { var ERROR_FLAG = -1; @@ -49,13 +73,18 @@ namespace Rokojori var lexerEvent = new LexerEvent( "", 0, -2 ); - while ( offset < source.Length ) + var numTries = 0; + var maxTries = 1000; + + while ( offset < source.Length && numTries < maxTries) { + numTries ++; + if ( ! _modes.ContainsKey( mode ) ) { var errorMessage = "@Lexer-Error. Mode not found: '" + mode + "'"; RJLog.Log( errorMessage, "@", offset ); - lexerEvent.set( errorMessage, offset, ERROR_FLAG ); + lexerEvent.Set( errorMessage, offset, ERROR_FLAG ); _hasError = true; callback( lexerEvent ); @@ -72,7 +101,7 @@ namespace Rokojori if ( matchLength > 0 ) { - lexerEvent.set( matcher.type, offset, matchLength ); + lexerEvent.Set( matcher.type, offset, matchLength ); //Logs.Log(matcher.type, ">>", "'"+source.Substring( offset, matchLength )+"'", "@", offset, matchLength ); callback( lexerEvent ); @@ -94,7 +123,7 @@ namespace Rokojori { var errorMessage = "@Lexer-Error. No match: '" + mode + "'"; RJLog.Log(errorMessage, "@", offset ); - lexerEvent.set( errorMessage, offset, ERROR_FLAG ); + lexerEvent.Set( errorMessage, offset, ERROR_FLAG ); _hasError = true; callback( lexerEvent ); @@ -103,7 +132,13 @@ namespace Rokojori } - lexerEvent.set( mode, offset, DONE_FLAG ); + if ( numTries >= maxTries ) + { + lexerEvent.Set( mode, offset, ERROR_FLAG ); + callback( lexerEvent ); + } + + lexerEvent.Set( mode, offset, DONE_FLAG ); callback( lexerEvent ); } diff --git a/Runtime/Text/Lexing/LexerEvent.cs b/Runtime/Text/Lexing/LexerEvent.cs index 86696c1..b5e6837 100644 --- a/Runtime/Text/Lexing/LexerEvent.cs +++ b/Runtime/Text/Lexing/LexerEvent.cs @@ -1,7 +1,7 @@ using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; - +using System.Text; namespace Rokojori @@ -17,7 +17,7 @@ namespace Rokojori public LexerEvent( string type, int offset, int length ) { - set( type, offset, length ); + Set( type, offset, length ); } public string type { get { return _type; } } @@ -34,7 +34,7 @@ namespace Rokojori get { return this.length == -2; } } - public void set( string type, int offset, int length ) + public void Set( string type, int offset, int length ) { this._type = type; this._offset = offset; @@ -65,5 +65,132 @@ namespace Rokojori _match = source.Substring( offset, length ); } + public bool Is( string type, string match = null ) + { + var typeCorrect = type == null || type == this.type; + + if ( ! typeCorrect ) + { + return false; + } + + return match == null || match == this.match; + } + + public bool Is( LexerMatcher matcher, string match = null ) + { + return Is( matcher == null ? null : matcher.type, match ); + } + + + public enum FindResultType + { + Found, + KeepSearching, + NotFound, + Error + } + + public class FindResult + { + public FindResultType type = FindResultType.NotFound; + public int index; + } + + public static string GetMatchFromRange( List tokens, int offset, int length ) + { + var match = new StringBuilder(); + + for ( int i = 0; i < length; i++ ) + { + match.Append( tokens[ offset + i ].match ); + } + + return match.ToString(); + } + + + public static FindResult Find( List tokens, int offset, System.Func evaluator ) + { + var result = new FindResult(); + + for ( int i = offset; i < tokens.Count; i++ ) + { + var tokenResult = evaluator( tokens[ i ] ); + + if ( tokenResult == FindResultType.Error || + tokenResult == FindResultType.Found + ) + { + result.type = tokenResult; + result.index = i; + + return result; + } + + } + + return result; + } + + public static FindResult FindClosingBracket( List tokens, int offset ) + { + var openTypes = new List(){ "(","[","{" }; + var closingTypes = new List(){ ")","]","}" }; + + var token = tokens[ offset ]; + + var bracketIndex = openTypes.IndexOf( token.match ); + + if ( bracketIndex == -1 ) + { + var result = new FindResult(); + result.type = FindResultType.NotFound; + result.index = offset; + return result; + } + + var opener = openTypes[ bracketIndex ]; + var closer = closingTypes[ bracketIndex ]; + + var counter = ( LexerEvent le ) => + { + if ( le.Is( LexerMatcherLibrary.BracketMatcher, closer ) ) + { + return -1; + } + + if ( le.Is( LexerMatcherLibrary.BracketMatcher, opener ) ) + { + return 1; + } + + return 0; + }; + + return FindCloser( tokens, offset, counter ); + } + + public static FindResult FindCloser( List tokens, int offset, System.Func counter ) + { + var result = new FindResult(); + var currentValue = 0; + + for ( int i = offset; i < tokens.Count; i++ ) + { + var countResult = counter( tokens[ i ] ); + + currentValue += countResult; + + if ( currentValue == 0 ) + { + result.type = FindResultType.Found; + result.index = i; + return result; + } + } + + return result; + } } } \ No newline at end of file diff --git a/Runtime/Text/Lexing/LexerLibrary/CSharpLexer.cs b/Runtime/Text/Lexing/LexerLibrary/CSharpLexer.cs index 6aad395..0565457 100644 --- a/Runtime/Text/Lexing/LexerLibrary/CSharpLexer.cs +++ b/Runtime/Text/Lexing/LexerLibrary/CSharpLexer.cs @@ -23,24 +23,24 @@ namespace Rokojori public CSharpLexer() { AddAllMatchers( - LexerMatcherLibrary.SINGLE_LINE_COMMENT_MATCHER, - LexerMatcherLibrary.MULTI_LINE_COMMENT_MATCHER, - LexerMatcherLibrary.DOUBLE_QUOTED_STRING_MATCHER, - LexerMatcherLibrary.SINGLE_QUOTED_STRING_MATCHER, - LexerMatcherLibrary.C_INSTRUCTION_MATCHER, - LexerMatcherLibrary.NUMBER_MATCHER, - LexerMatcherLibrary.NULL_MATCHER, - LexerMatcherLibrary.BOOL_MATCHER, - LexerMatcherLibrary.BREAK_MATCHER, - LexerMatcherLibrary.WHITESPACE_MATCHER, - LexerMatcherLibrary.LOGIC_MATCHER, - LexerMatcherLibrary.BRACKET_MATCHER, - LexerMatcherLibrary.ACCESS_MODIFIER_MATCHER, - LexerMatcherLibrary.CLASS_MATCHER, - LexerMatcherLibrary.OPERATOR_MATCHER, - LexerMatcherLibrary.CFUNCTION_MATCHER, - LexerMatcherLibrary.CWORD_MATCHER, - LexerMatcherLibrary.ANY_SYMBOL_MATCHER + LexerMatcherLibrary.SingleLineCommentMatcher, + LexerMatcherLibrary.MultiLineCommentMatcher, + LexerMatcherLibrary.DoubleQuotedStringMatcher, + LexerMatcherLibrary.SingleQuotedStringMatcher, + LexerMatcherLibrary.CInstructionMatcher, + LexerMatcherLibrary.NumberMatcher, + LexerMatcherLibrary.NullMatcher, + LexerMatcherLibrary.BoolMatcher, + LexerMatcherLibrary.BreakMatcher, + LexerMatcherLibrary.WhiteSpaceMatcher, + LexerMatcherLibrary.LogicMatcher, + LexerMatcherLibrary.BracketMatcher, + LexerMatcherLibrary.AccessModifierMatcher, + LexerMatcherLibrary.ClassMatcher, + LexerMatcherLibrary.OperatorMatcher, + LexerMatcherLibrary.CFunctionMatcher, + LexerMatcherLibrary.CwordMatcher, + LexerMatcherLibrary.AnySymbolMatcher ); } } diff --git a/Runtime/Text/Lexing/LexerMatcher.cs b/Runtime/Text/Lexing/LexerMatcher.cs index 48c5269..e32ae57 100644 --- a/Runtime/Text/Lexing/LexerMatcher.cs +++ b/Runtime/Text/Lexing/LexerMatcher.cs @@ -42,6 +42,11 @@ namespace Rokojori _matcher = RegexUtility.MakeSticky( new Regex( matcher ) ); } + public static LexerMatcher CreateExtended( string type, string matcher, string mode = "", string nextMode = "" ) + { + return new LexerMatcher( type, RegexExtensions.Extend( matcher ), mode, nextMode ); + } + public LexerMatcher CloneWithMode( string mode, string nextMode ) { return new LexerMatcher( _type, _matcher, mode, nextMode ); diff --git a/Runtime/Text/Lexing/LexerMatcherLibrary.cs b/Runtime/Text/Lexing/LexerMatcherLibrary.cs index 665ede8..d25d694 100644 --- a/Runtime/Text/Lexing/LexerMatcherLibrary.cs +++ b/Runtime/Text/Lexing/LexerMatcherLibrary.cs @@ -6,93 +6,90 @@ namespace Rokojori { public class LexerMatcherLibrary { - public static readonly LexerMatcher CWORD_MATCHER = - new LexerMatcher( "CWORD", @"[a-zA-Z_]\w*" ); + public static readonly LexerMatcher CwordMatcher = + new LexerMatcher( "CWord", @"[a-zA-Z_]\w*" ); - public static readonly LexerMatcher CFUNCTION_MATCHER = - new LexerMatcher( "CFUNCTION", @"[a-zA-Z_]\w*(?=\s*\()" ); + public static readonly LexerMatcher CFunctionMatcher = + new LexerMatcher( "CFunction", @"[a-zA-Z_]\w*(?=\s*\()" ); - // public static readonly LexerMatcher LexerMatcherCLASSNAME_MATCHER = - // new LexerMatcher( "CLASSNAME", @"(?<=(?:(?:class|interface|struct)\s+))[a-zA-Z_]\w*" ); + public static readonly LexerMatcher JSWordMatcher = + new LexerMatcher( "JSWord", @"[a-zA-Z_\$]\w*" ); - public static readonly LexerMatcher JSWORD_MATCHER = - new LexerMatcher( "JSWORD", @"[a-zA-Z_\$]\w*" ); - - public static readonly LexerMatcher PHPWORD_MATCHER = - new LexerMatcher( "PHPWORD", @"\$?[a-zA-Z_]\w*" ); + public static readonly LexerMatcher PHPWordMatcher = + new LexerMatcher( "PHPWord", @"\$?[a-zA-Z_]\w*" ); - public static readonly LexerMatcher CSS_CLASS_SELECTOR_MATCHER = - new LexerMatcher( "CSS_CLASS_SELECTOR", @"\.[a-zA-Z_](\w|\-)*" ); + public static readonly LexerMatcher CSS_ClassSelectorMatcher = + new LexerMatcher( "CSS_ClassSelector", @"\.[a-zA-Z_](\w|\-)*" ); - public static readonly LexerMatcher CSS_ID_SELECTOR_MATCHER = - new LexerMatcher( "CSS_ID_SELECTOR", @"\#[a-zA-Z_](\w|\-)*" ); + public static readonly LexerMatcher CSS_IDSelectorMatcher = + new LexerMatcher( "CSS_IDSelector", @"\#[a-zA-Z_](\w|\-)*" ); - public static readonly LexerMatcher CSS_WORD_MATCHER = - new LexerMatcher( "CSS_WORD", @"[a-zA-Z_](\w|\-)*" ); + public static readonly LexerMatcher CSS_WordMatcher = + new LexerMatcher( "CSS_Word", @"[a-zA-Z_](\w|\-)*" ); - public static readonly LexerMatcher HTML_CUSTOM_ELEMENT_MATCHER = - new LexerMatcher( "HTML_CUSTOM_ELEMENT", @"[a-zA-Z]\w*\-(\w|\-)+" ); + public static readonly LexerMatcher HTML_CustomElementMatcher = + new LexerMatcher( "HTML_CustomElement", @"[a-zA-Z]\w*\-(\w|\-)+" ); - public static readonly LexerMatcher DOUBLE_QUOTED_STRING_MATCHER = - new LexerMatcher( "DOUBLE_QUOTED_STRING", "\"(?:[^\"\\\\]|\\\\.)*\"" ); + public static readonly LexerMatcher DoubleQuotedStringMatcher = + new LexerMatcher( "DoubleQuotedString", "\"(?:[^\"\\\\]|\\\\.)*\"" ); - public static readonly LexerMatcher SINGLE_QUOTED_STRING_MATCHER = - new LexerMatcher( "SINGLE_QUOTED_STRING", @"'(?:[^'\\]|\\.)*'" ); + public static readonly LexerMatcher SingleQuotedStringMatcher = + new LexerMatcher( "SingleQuotedString", @"'(?:[^'\\]|\\.)*'" ); - public static readonly LexerMatcher NUMBER_MATCHER = - new LexerMatcher( "NUMBER", @"(?=\.\d|\d)(?:\d+)?(?:\.?\d*)(?:[eE][+-]?\d+)?" ); + public static readonly LexerMatcher NumberMatcher = + new LexerMatcher( "Number", @"(?=\.\d|\d)(?:\d+)?(?:\.?\d*)(?:[eE][+-]?\d+)?" ); - public static readonly LexerMatcher WHITESPACE_MATCHER = - new LexerMatcher( "WHITESPACE", @"\s+" ); + public static readonly LexerMatcher WhiteSpaceMatcher = + new LexerMatcher( "WhiteSpace", @"\s+" ); - public static readonly LexerMatcher BREAK_MATCHER = - new LexerMatcher( "BREAK", @"(\r\n|\r|\n)" ); + public static readonly LexerMatcher BreakMatcher = + new LexerMatcher( "Break", @"(\r\n|\r|\n)" ); - public static readonly LexerMatcher NULL_MATCHER = - new LexerMatcher( "NULL", "null" ); + public static readonly LexerMatcher NullMatcher = + new LexerMatcher( "Null", "null" ); - public static readonly LexerMatcher BOOL_MATCHER = - new LexerMatcher( "BOOL", "true|false" ); + public static readonly LexerMatcher BoolMatcher = + new LexerMatcher( "Bool", "true|false" ); - public static readonly LexerMatcher LOGIC_MATCHER = - new LexerMatcher( "LOGIC", "if|else|switch|do|while|for|break|continue|return" ); + public static readonly LexerMatcher LogicMatcher = + new LexerMatcher( "Logic", "if|else|switch|do|while|for|break|continue|return" ); - public static readonly LexerMatcher OPERATOR_MATCHER = - new LexerMatcher( "OPERATOR", "(?:\\=\\=)|(?:\\+\\+)|(?:\\-\\-)|\\+|\\-|\\*|\\/|\\^|\\||\\~|\\&|\\%|\\<|\\>|\\=|\\!|\\.|\\:|\\,|\\;" ); + public static readonly LexerMatcher OperatorMatcher = + new LexerMatcher( "Operator", "(?:\\=\\=)|(?:\\+\\+)|(?:\\-\\-)|\\+|\\-|\\*|\\/|\\^|\\||\\~|\\&|\\%|\\<|\\>|\\=|\\!|\\.|\\:|\\,|\\;" ); - public static readonly LexerMatcher BRACKET_MATCHER = - new LexerMatcher( "BRACKET", @"\(|\)|\[|\]|\{|\}" ); + public static readonly LexerMatcher BracketMatcher = + new LexerMatcher( "Bracket", @"\(|\)|\[|\]|\{|\}" ); - public static readonly LexerMatcher BLOCKSTART_MATCHER = - new LexerMatcher( "BLOCKSTART", @"\{" ); + public static readonly LexerMatcher BlockStartMatcher = + new LexerMatcher( "BlockStart", @"\{" ); - public static readonly LexerMatcher BLOCKEND_MATCHER = - new LexerMatcher( "BLOCKEND", @"\}" ); + public static readonly LexerMatcher BlockEndMatcher = + new LexerMatcher( "BlockStart", @"\}" ); - public static readonly LexerMatcher CLASS_MATCHER = - new LexerMatcher( "CLASS", @"\bclass\b" ); + public static readonly LexerMatcher ClassMatcher = + new LexerMatcher( "Class", @"\bclass\b" ); - public static readonly LexerMatcher ACCESS_MODIFIER_MATCHER = - new LexerMatcher( "ACCESS_MODIFIER", @"\b(?:public|protected|private)\b" ); + public static readonly LexerMatcher AccessModifierMatcher = + new LexerMatcher( "AccessModifier", @"\b(?:public|protected|private)\b" ); - public static readonly LexerMatcher SINGLE_LINE_COMMENT_MATCHER = - new LexerMatcher( "SINGLE_LINE_COMMENT", @"//.*" ); + public static readonly LexerMatcher SingleLineCommentMatcher = + new LexerMatcher( "SingleLineComment", @"//.*" ); - public static readonly LexerMatcher C_INSTRUCTION_MATCHER = - new LexerMatcher( "C_INSTRUCTION", @"\#.*" ); + public static readonly LexerMatcher CInstructionMatcher = + new LexerMatcher( "CInstruction", @"\#.*" ); - public static readonly LexerMatcher MULTI_LINE_COMMENT_MATCHER = - new LexerMatcher( "MULTI_LINE_COMMENT", @"\/\*(.|(\r\n|\r|\n))*?\*\/" ); + public static readonly LexerMatcher MultiLineCommentMatcher = + new LexerMatcher( "MultiLineComment", @"\/\*(.|(\r\n|\r|\n))*?\*\/" ); - public static readonly LexerMatcher ANY_SYMBOL_MATCHER = - new LexerMatcher( "ANY_SYMBOL", @"." ); + public static readonly LexerMatcher AnySymbolMatcher = + new LexerMatcher( "AnySymbol", @"." ); - public static readonly LexerMatcher HASH_TAG = - new LexerMatcher( "HASH_TAG", @"\#(\w|-|\d)+" ); + public static readonly LexerMatcher HashTag = + new LexerMatcher( "HashTag", @"\#(\w|-|\d)+" ); public static readonly LexerMatcher URL = new LexerMatcher( "URL", @"https?\:\/\/(\w|\.|\-|\?|\=|\+|\/)+" ); diff --git a/Runtime/Text/RegexExtensions.cs b/Runtime/Text/RegexExtensions.cs new file mode 100644 index 0000000..4ec7227 --- /dev/null +++ b/Runtime/Text/RegexExtensions.cs @@ -0,0 +1,28 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Text; + +using System.Globalization; +using Godot; + +namespace Rokojori +{ + public class RegexExtensions + { + public static string Extend( string source ) + { + //RJLog.Log( "Original:", source ); + + // l: Linebreak + source = RegexUtility.Replace( source, @"\\l", @"(?:\r\n|\r|\n)" ); + + // v: Variable + source = RegexUtility.Replace( source, @"\\l", @"(?:\w|_)+" ); + + //RJLog.Log( "Extended:", source ); + + return source; + } + } +} \ No newline at end of file diff --git a/Runtime/Text/RegexUtility.cs b/Runtime/Text/RegexUtility.cs index 3fcc3cc..58b0382 100644 --- a/Runtime/Text/RegexUtility.cs +++ b/Runtime/Text/RegexUtility.cs @@ -384,6 +384,14 @@ namespace Rokojori return Split( source, new Regex( regex ) ); } + public static List SplitLines( string source ) + { + var array = Regex.Split( source, "\r\n|\r|\n" ); + var list = new List(); + list.AddRange( array ); + return list; + } + public static string Remove( string source, string regex ) { return Replace( source, regex, "" ); diff --git a/Runtime/VirtualCameras/MouseEditorCamera.cs b/Runtime/VirtualCameras/MouseEditorCamera.cs index 016701a..caa763b 100644 --- a/Runtime/VirtualCameras/MouseEditorCamera.cs +++ b/Runtime/VirtualCameras/MouseEditorCamera.cs @@ -14,6 +14,7 @@ namespace Rokojori { [Export] public Vector3 target; + [Export] public float yaw = 0; diff --git a/Runtime/VirtualCameras/StrategyTopDownCamera.cs b/Runtime/VirtualCameras/StrategyTopDownCamera.cs new file mode 100644 index 0000000..2802c73 --- /dev/null +++ b/Runtime/VirtualCameras/StrategyTopDownCamera.cs @@ -0,0 +1,226 @@ + +using System.Diagnostics; +using System.Collections; +using System.Collections.Generic; +using System; +using Godot; + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class StrategyTopDownCamera:VirtualCamera3D + { + [Export] + public Vector3 target; + + [Export] + public float yaw = 0; + + [Export] + public float pitch = 0; + + [Export] + public float distance = 10; + float smoothDistance = 10; + + [ExportCategory("Move")] + + [Export] + public RJSensor moveUpButton; + [Export] + public RJSensor moveDownButton; + [Export] + public RJSensor moveRightButton; + [Export] + public RJSensor moveLeftButton; + + [Export] + public float buttonMoveSpeed = 1f; + + + [Export] + public RJSensor mouseMoveButton; + + [Export] + public float mouseMoveSpeed = 0.001f; + + [Export] + public bool flipHorizontal = false; + + [Export] + public bool flipVertical = false; + + [Export] + public bool moveAtBorders = true; + + [Export] + public float moveAtBorderSpeed = 0.001f; + + [Export] + public float borderSizePercentage = 5; + + [ExportCategory("Orbit")] + + [Export] + public RJSensor orbitRightButton; + + [Export] + public RJSensor orbitLeftButton; + + [Export] + public RJSensor orbitMouseButton; + + [Export] + public float mouseOrbitSpeedScale = -0.5f; + + [Export] + public float orbitSpeed = 0.001f; + + + [ExportCategory("Zoom")] + + [Export] + public float zoomStepInPercentage = 10; + + [Export] + public float minDistance = 0.001f; + + [Export] + public float maxDistance = 200f; + + [Export] + public RJSensor zoomInButton; + + [Export] + public RJSensor[] zoomInModifierButtons; + + [Export] + public RJSensor zoomOutButton; + + [Export] + public RJSensor[] zoomOutModifierButtons; + + [Export] + public float zoomSmoothingCoefficient = 0.1f; + Smoother smoother = new Smoother(); + + public override void _Process( double delta ) + { + Move(); + Orbit( (float) delta ); + Zoom(); + + Apply( (float) delta ); + + if ( ! hasMotionDelta ) + { + motionDelta.X = 0; + motionDelta.Y = 0; + } + + hasMotionDelta = false; + } + + bool hasMotionDelta = false; + Vector2 motionDelta = Vector2.Zero; + + float borderMoveHorizontal = 0; + float borderMoveVertical = 0; + + float GetBorderSize() + { + var size = GetViewport().GetVisibleRect().Size; + + var borderX = ( size.X * borderSizePercentage ) / 100f; + var borderY = ( size.Y * borderSizePercentage ) / 100f; + + return Mathf.Min( borderX, borderY ); + } + + public override void _Input( InputEvent inputEvent ) + { + if ( inputEvent is InputEventMouseMotion ) + { + var eventMouseMotion = inputEvent as InputEventMouseMotion; + motionDelta = eventMouseMotion.ScreenRelative; + + if ( moveAtBorders ) + { + var position = eventMouseMotion.Position; + + var screenSize = GetViewport().GetVisibleRect().Size; + var borderSize = GetBorderSize(); + + borderMoveHorizontal = - MathX.PolarAxis( position.X < borderSize, position.X > screenSize.X - borderSize ); + borderMoveVertical = - MathX.PolarAxis( position.Y < borderSize, position.Y > screenSize.Y - borderSize ); + } + + hasMotionDelta = true; + } + + } + + + void Move() + { + var deltaX = 0f; + var deltaY = 0f; + + if ( Sensors.IsActive( mouseMoveButton ) ) + { + deltaX = motionDelta.X * mouseMoveSpeed; + deltaY = motionDelta.Y * mouseMoveSpeed; + } + else + { + deltaX = Sensors.PolarAxis( moveLeftButton, moveRightButton ) + borderMoveHorizontal * moveAtBorderSpeed; + deltaY = Sensors.PolarAxis( moveUpButton, moveDownButton ) + borderMoveVertical * moveAtBorderSpeed; + } + + var forward = Math3D.GetYPlaneForward( this ); + var right = Math3D.GetYPlaneRight( this ); + + var flipH = flipHorizontal ? -1 : 1; + var flipV = flipVertical ? -1 : 1; + + var xAmount = deltaX * smoothDistance * right * flipH; + var zAmount = deltaY * smoothDistance * forward * flipV; + + target += ( xAmount + zAmount ); + + } + + void Orbit( float delta ) + { + var direction = Sensors.PolarAxis( orbitLeftButton, orbitRightButton ); + + if ( Sensors.IsActive( orbitMouseButton ) ) + { + yaw += motionDelta.X * mouseOrbitSpeedScale; + } + + yaw += direction * orbitSpeed * delta; + } + + void Zoom() + { + var zoomButtonAxis = Sensors.PolarAxis( zoomOutButton, zoomInButton ); + + distance *= Mathf.Pow( 1 + zoomStepInPercentage / 100f, zoomButtonAxis ); + + distance = Mathf.Clamp( distance, minDistance, maxDistance ); + } + + + void Apply( float delta ) + { + smoothDistance = smoother.SmoothWithCoefficient( smoothDistance, distance, zoomSmoothingCoefficient, delta ); + GlobalRotation = new Vector3( Mathf.DegToRad( pitch ), Mathf.DegToRad( yaw ), 0 ); + + var forward = Math3D.GetGlobalForward( this ) * smoothDistance; + GlobalPosition = target - forward; + } + } +} \ No newline at end of file diff --git a/Runtime/VirtualCameras/VirtualCamera3DManager.cs b/Runtime/VirtualCameras/VirtualCamera3DManager.cs index 8cc543b..b1557ca 100644 --- a/Runtime/VirtualCameras/VirtualCamera3DManager.cs +++ b/Runtime/VirtualCameras/VirtualCamera3DManager.cs @@ -69,6 +69,7 @@ namespace Rokojori var position = new Vector3(); var up = new Vector3(); var forward = new Vector3(); + var fov = 0f; _cameraSlots.ForEach( c => @@ -100,10 +101,12 @@ namespace Rokojori position += priority * c.camera.GetCameraPosition(); up += priority * vUp; forward += priority * vForward; + fov += priority * c.camera.GetCameraFOV(); } ); position /= sumPriority; + fov /= sumPriority; if ( forward.LengthSquared() == 0 ) { @@ -126,6 +129,7 @@ namespace Rokojori camera.GlobalPosition = position; camera.LookAt( position - forward, up ); + camera.Fov = fov; }