Added textures to biomes and an expression parser.
This commit is contained in:
		
							
								
								
									
										425
									
								
								Game/Assets/Scripts/Utils/Expression.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										425
									
								
								Game/Assets/Scripts/Utils/Expression.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,425 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
 | 
			
		||||
namespace GraphingCalculator
 | 
			
		||||
{
 | 
			
		||||
    public class Expression
 | 
			
		||||
    {
 | 
			
		||||
        #region Types
 | 
			
		||||
 | 
			
		||||
        public enum TokenType
 | 
			
		||||
        {
 | 
			
		||||
            None, Literal, Identifier, Operator, ArgSeparator,
 | 
			
		||||
            LParanthesis, RParanthesis, Function
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Represents a token
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        protected class Token
 | 
			
		||||
        {
 | 
			
		||||
            public TokenType Type { get; set; }
 | 
			
		||||
            public string Text { get; set; }
 | 
			
		||||
 | 
			
		||||
            public Token(TokenType type, string text)
 | 
			
		||||
            {
 | 
			
		||||
                Type = type;
 | 
			
		||||
                Text = text;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        #region Variables, properties
 | 
			
		||||
        
 | 
			
		||||
        private string expressionString;
 | 
			
		||||
        private bool parsed = false;
 | 
			
		||||
 | 
			
		||||
        protected List<Token> Tokens { get; private set; }
 | 
			
		||||
        protected List<Token> Postfix { get; private set; }
 | 
			
		||||
        public Dictionary<string, float> Variables { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public string ExpressionString
 | 
			
		||||
        {
 | 
			
		||||
            get { return expressionString; }
 | 
			
		||||
            set
 | 
			
		||||
            {
 | 
			
		||||
                expressionString = value;
 | 
			
		||||
                parsed = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        #region Other stuff
 | 
			
		||||
 | 
			
		||||
        public void AddVariable(string name, float value)
 | 
			
		||||
        {
 | 
			
		||||
            Variables.Add(name, value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        #region Constructor
 | 
			
		||||
        public Expression()
 | 
			
		||||
        {
 | 
			
		||||
            Tokens = new List<Token>();
 | 
			
		||||
            Postfix = new List<Token>();
 | 
			
		||||
            Variables = new Dictionary<string, float>();
 | 
			
		||||
 | 
			
		||||
            Variables.Add("pi", Mathf.PI);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Expression(string expr)
 | 
			
		||||
            : this()
 | 
			
		||||
        {
 | 
			
		||||
            expressionString = expr;
 | 
			
		||||
        }
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        #region Parser
 | 
			
		||||
 | 
			
		||||
        private void AnalyzeLex()
 | 
			
		||||
        {
 | 
			
		||||
            Tokens.Clear();
 | 
			
		||||
 | 
			
		||||
            for (int i = 0; i < ExpressionString.Length; i++)
 | 
			
		||||
            {
 | 
			
		||||
                if (char.IsWhiteSpace(ExpressionString[i])) continue;
 | 
			
		||||
 | 
			
		||||
                if (IsOperator(ExpressionString[i]))
 | 
			
		||||
                {
 | 
			
		||||
                    // Handle unary minus
 | 
			
		||||
                    if (IsUnaryMinus(ExpressionString[i]))
 | 
			
		||||
                        Tokens.Add(new Token(TokenType.Operator, "u"));
 | 
			
		||||
 | 
			
		||||
                    else
 | 
			
		||||
                        Tokens.Add(new Token(TokenType.Operator, ExpressionString[i].ToString()));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                else if (ExpressionString[i] == '(')
 | 
			
		||||
                    Tokens.Add(new Token(TokenType.LParanthesis, ExpressionString[i].ToString()));
 | 
			
		||||
 | 
			
		||||
                else if (ExpressionString[i] == ')')
 | 
			
		||||
                    Tokens.Add(new Token(TokenType.RParanthesis, ExpressionString[i].ToString()));
 | 
			
		||||
 | 
			
		||||
                else if (ExpressionString[i] == ',')
 | 
			
		||||
                    Tokens.Add(new Token(TokenType.ArgSeparator, ExpressionString[i].ToString()));
 | 
			
		||||
 | 
			
		||||
                else if (Char.IsDigit(ExpressionString[i]))
 | 
			
		||||
                    Tokens.Add(new Token(TokenType.Literal, GetLiteral(ExpressionString, ref i)));
 | 
			
		||||
 | 
			
		||||
                else if (Char.IsLetter(ExpressionString[i]))
 | 
			
		||||
                    Tokens.Add(new Token(TokenType.Identifier, GetIdentifier(ExpressionString, ref i)));
 | 
			
		||||
 | 
			
		||||
                else throw new Exception("Unrecognized character found!");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ConvertPostfix()
 | 
			
		||||
        {
 | 
			
		||||
            Stack<Token> stack = new Stack<Token>();
 | 
			
		||||
 | 
			
		||||
            for (int i = 0; i < Tokens.Count; i++)
 | 
			
		||||
            {
 | 
			
		||||
                Token t = Tokens[i];
 | 
			
		||||
 | 
			
		||||
                switch (t.Type)
 | 
			
		||||
                {
 | 
			
		||||
                    case TokenType.Identifier:
 | 
			
		||||
                        
 | 
			
		||||
                        // Followed by '(' means function
 | 
			
		||||
                        if (i + 1 < Tokens.Count && Tokens[i + 1].Type == TokenType.LParanthesis)
 | 
			
		||||
                            stack.Push(new Token(TokenType.Function, t.Text));
 | 
			
		||||
 | 
			
		||||
                        // Else, variable
 | 
			
		||||
                        else Postfix.Add(t);
 | 
			
		||||
                        
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case TokenType.Literal: 
 | 
			
		||||
                        Postfix.Add(t);
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case TokenType.ArgSeparator:
 | 
			
		||||
 | 
			
		||||
                        // We pop everything from the stack until left paranthesis open
 | 
			
		||||
                        while (stack.Peek().Type != TokenType.LParanthesis)
 | 
			
		||||
                        {
 | 
			
		||||
                            Postfix.Add(stack.Pop());
 | 
			
		||||
                            
 | 
			
		||||
                            if (stack.Count == 0) 
 | 
			
		||||
                                throw new Exception("Syntax error! Unexpected comma.");
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case TokenType.Operator:
 | 
			
		||||
 | 
			
		||||
                        if (IsLeftAssociative(t.Text))
 | 
			
		||||
                        {
 | 
			
		||||
                            while (stack.Count != 0 && Precedence(t.Text) <= Precedence(stack.Peek().Text))
 | 
			
		||||
                                Postfix.Add(stack.Pop());
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            while (stack.Count != 0 && Precedence(t.Text) < Precedence(stack.Peek().Text))
 | 
			
		||||
                                Postfix.Add(stack.Pop());
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        stack.Push(t);
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case TokenType.LParanthesis: 
 | 
			
		||||
                        stack.Push(t); 
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case TokenType.RParanthesis:
 | 
			
		||||
 | 
			
		||||
                        while (stack.Peek().Type != TokenType.LParanthesis)
 | 
			
		||||
                        {
 | 
			
		||||
                            Postfix.Add(stack.Pop());
 | 
			
		||||
                            
 | 
			
		||||
                            if (stack.Count == 0) 
 | 
			
		||||
                                throw new Exception("Mismatched parantheses!");
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        stack.Pop(); // Pop Lparanthesis
 | 
			
		||||
 | 
			
		||||
                        if (stack.Count > 0 && stack.Peek().Type == TokenType.Function) 
 | 
			
		||||
                            Postfix.Add(stack.Pop());   // Pop function name
 | 
			
		||||
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            while (stack.Count > 0)
 | 
			
		||||
            {
 | 
			
		||||
                if (stack.Peek().Type == TokenType.LParanthesis)
 | 
			
		||||
                    throw new Exception("Mismatched parantheses!");
 | 
			
		||||
                
 | 
			
		||||
                Postfix.Add(stack.Pop());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void ParseExpression()
 | 
			
		||||
        {
 | 
			
		||||
            if (!parsed)
 | 
			
		||||
            {
 | 
			
		||||
                Tokens.Clear();
 | 
			
		||||
                Postfix.Clear();
 | 
			
		||||
 | 
			
		||||
                AnalyzeLex();
 | 
			
		||||
                ConvertPostfix();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public float Evaluate()
 | 
			
		||||
        {
 | 
			
		||||
            // Parse expression first
 | 
			
		||||
            ParseExpression();
 | 
			
		||||
            
 | 
			
		||||
            // Expression is empty, so is result
 | 
			
		||||
            if (Postfix.Count == 0) 
 | 
			
		||||
                return 0;
 | 
			
		||||
 | 
			
		||||
            Stack<float> stack = new Stack<float>();
 | 
			
		||||
 | 
			
		||||
            foreach (var t in Postfix)
 | 
			
		||||
            {
 | 
			
		||||
                switch (t.Type)
 | 
			
		||||
                {
 | 
			
		||||
                    // We already replace functions, so identifiers are variables
 | 
			
		||||
                    case TokenType.Identifier:
 | 
			
		||||
                        
 | 
			
		||||
                        if (!Variables.ContainsKey(t.Text))
 | 
			
		||||
                            throw new Exception("Undefined variable '" + t.Text + "'.");
 | 
			
		||||
 | 
			
		||||
                        stack.Push(Variables[t.Text]);
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case TokenType.Literal: 
 | 
			
		||||
                        stack.Push(float.Parse(t.Text)); 
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case TokenType.Operator:
 | 
			
		||||
                        switch (t.Text)
 | 
			
		||||
                        {
 | 
			
		||||
                            case "u": 
 | 
			
		||||
                                stack.Push(stack.Pop() * -1); 
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            case "+": 
 | 
			
		||||
                                stack.Push(stack.Pop() + stack.Pop());
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            case "-":
 | 
			
		||||
                                {
 | 
			
		||||
                                    float b = stack.Pop();
 | 
			
		||||
                                    float a = stack.Pop();
 | 
			
		||||
                                    stack.Push(a - b);
 | 
			
		||||
                                } 
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            case "*": 
 | 
			
		||||
                                stack.Push(stack.Pop() * stack.Pop()); 
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            case "/":
 | 
			
		||||
                                {
 | 
			
		||||
                                    float b = stack.Pop();
 | 
			
		||||
                                    float a = stack.Pop();
 | 
			
		||||
                                    stack.Push(a / b);
 | 
			
		||||
                                }
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            case "%":
 | 
			
		||||
                                {
 | 
			
		||||
                                    float b = stack.Pop();
 | 
			
		||||
                                    float a = stack.Pop();
 | 
			
		||||
                                    stack.Push(a % b);
 | 
			
		||||
                                }
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            case "^":
 | 
			
		||||
                                {
 | 
			
		||||
                                    float b = stack.Pop();
 | 
			
		||||
                                    float a = stack.Pop();
 | 
			
		||||
                                    stack.Push(Mathf.Pow(a, b));
 | 
			
		||||
                                }
 | 
			
		||||
                                break;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case TokenType.Function:
 | 
			
		||||
                        EvaluateFunction(t.Text, ref stack);
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return stack.Pop();
 | 
			
		||||
        }
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        #region Helper routines
 | 
			
		||||
 | 
			
		||||
        private bool IsUnaryMinus(char c)
 | 
			
		||||
        {
 | 
			
		||||
            if (c == '-')
 | 
			
		||||
            {
 | 
			
		||||
                // Nothing in front, definitely unary
 | 
			
		||||
                if (Tokens.Count == 0)
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
                // See what's in front
 | 
			
		||||
                TokenType inFront = Tokens.Last().Type;
 | 
			
		||||
 | 
			
		||||
                // If what's in front cannot be an operand, than it is unary minus
 | 
			
		||||
                return inFront == TokenType.ArgSeparator || inFront == TokenType.LParanthesis || inFront == TokenType.Operator;
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void EvaluateFunction(string func, ref Stack<float> stack)
 | 
			
		||||
        {
 | 
			
		||||
            switch (func)
 | 
			
		||||
            {
 | 
			
		||||
                case "sin": stack.Push(Mathf.Sin(stack.Pop())); break;
 | 
			
		||||
                case "cos": stack.Push(Mathf.Cos(stack.Pop())); break;
 | 
			
		||||
                case "tan": stack.Push(Mathf.Tan(stack.Pop())); break;
 | 
			
		||||
                case "ctan": stack.Push(1 / Mathf.Tan(stack.Pop())); break;
 | 
			
		||||
 | 
			
		||||
                case "arcsin": stack.Push(Mathf.Asin(stack.Pop())); break;
 | 
			
		||||
                case "asin": stack.Push(Mathf.Asin(stack.Pop())); break;
 | 
			
		||||
                case "arccos": stack.Push(Mathf.Acos(stack.Pop())); break;
 | 
			
		||||
                case "acos": stack.Push(Mathf.Acos(stack.Pop())); break;
 | 
			
		||||
                case "arctan": stack.Push(Mathf.Atan(stack.Pop())); break;
 | 
			
		||||
                case "atan": stack.Push(Mathf.Atan(stack.Pop())); break;
 | 
			
		||||
 | 
			
		||||
                case "truncate":
 | 
			
		||||
                case "floor": stack.Push(Mathf.Floor(stack.Pop())); break;
 | 
			
		||||
                case "ceil":
 | 
			
		||||
                case "ceiling": stack.Push(Mathf.Ceil(stack.Pop())); break;
 | 
			
		||||
 | 
			
		||||
                case "sqrt": stack.Push(Mathf.Sqrt(stack.Pop())); break;
 | 
			
		||||
                case "cbrt": stack.Push(Mathf.Pow(stack.Pop(), 1.0f / 3.0f)); break;
 | 
			
		||||
                case "root": stack.Push(Mathf.Pow(stack.Pop(), 1 / stack.Pop())); break;
 | 
			
		||||
 | 
			
		||||
                case "abs": stack.Push(Math.Abs(stack.Pop())); break;
 | 
			
		||||
                case "max": stack.Push(Math.Max(stack.Pop(), stack.Pop())); break;
 | 
			
		||||
                case "min": stack.Push(Math.Min(stack.Pop(), stack.Pop())); break;
 | 
			
		||||
 | 
			
		||||
                case "lg": stack.Push(Mathf.Log10(stack.Pop())); break;
 | 
			
		||||
                case "log": stack.Push(Mathf.Log(stack.Pop(), stack.Pop())); break;
 | 
			
		||||
                
 | 
			
		||||
                case "clamp01": stack.Push(Mathf.Clamp01(stack.Pop())); break;
 | 
			
		||||
                case "clamp": stack.Push(Mathf.Clamp(stack.Pop(), stack.Pop(), stack.Pop())); break;
 | 
			
		||||
                case "lerp": stack.Push(Mathf.Lerp(stack.Pop(), stack.Pop(), stack.Pop())); break;
 | 
			
		||||
 | 
			
		||||
                default: throw new Exception("Undefined function '" + func + "'.");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static bool IsLeftAssociative(string op)
 | 
			
		||||
        {
 | 
			
		||||
            return (op != "^");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static int Precedence(string op)
 | 
			
		||||
        {
 | 
			
		||||
            switch (op)
 | 
			
		||||
            {
 | 
			
		||||
                case "+":
 | 
			
		||||
                case "-": return 1;
 | 
			
		||||
 | 
			
		||||
                case "*":
 | 
			
		||||
                case "/":
 | 
			
		||||
                case "%": return 2;
 | 
			
		||||
 | 
			
		||||
                case "u":
 | 
			
		||||
                case "^": return 3;
 | 
			
		||||
 | 
			
		||||
                default: return 0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static bool IsOperator(char c)
 | 
			
		||||
        {
 | 
			
		||||
            const string operators = "+-*/%^";
 | 
			
		||||
            return operators.Contains(c);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static string GetIdentifier(string s, ref int index)
 | 
			
		||||
        {
 | 
			
		||||
            int start = index;
 | 
			
		||||
 | 
			
		||||
            while (index < s.Length && (char.IsLetterOrDigit(s[index]) || s[index] == '_'))
 | 
			
		||||
                ++index;
 | 
			
		||||
 | 
			
		||||
            index -= 1;
 | 
			
		||||
 | 
			
		||||
            return s.Substring(start, index + 1 - start);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static string GetLiteral(string s, ref int index)
 | 
			
		||||
        {
 | 
			
		||||
            int start = index;
 | 
			
		||||
 | 
			
		||||
            while (index < s.Length && (char.IsDigit(s[index]) || s[index] == '.'))
 | 
			
		||||
                ++index;
 | 
			
		||||
 | 
			
		||||
            index -= 1;
 | 
			
		||||
 | 
			
		||||
            return s.Substring(start, index + 1 - start);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user