using System; using System.Linq; using UnityEngine; namespace StixGames.GrassShader { /// /// This class is a base point for displacing the grass. /// It's really nothing else than a texture painter, with some vector transformations for the displacement. /// public static class GrassManipulationUtility { /// /// Returns the texture from the transform or throws a exception if it wasn't found. /// /// /// If true returns color/height texture instead of the displacement texture. /// public static Texture2D GetGrassTexture(Transform target, bool getColorHeight) { var material = target.GetComponent().material; var tex = material.GetTexture(getColorHeight ? "_ColorMap" : "_Displacement") as Texture2D; if (tex == null) { throw new InvalidOperationException("You have to add a texture (or texture-adder component) if you want to use dynamic grass."); } return tex; } /// /// This uses the forward and right vectors as a matrix and returns its reverse. /// /// /// /// /// public static void Invert2x2Matrix(Vector2 forward, Vector2 right, out Vector2 inverseForward, out Vector2 inverseRight) { float det = right.x * forward.y - right.y * forward.x; inverseRight = new Vector2(forward.y, -right.y) / det; inverseForward = new Vector2(-forward.x, right.x) / det; } /// /// Gets the 2x2 matrix that tranforms from world to texture space. /// Uses raycasts, so don't overuse it. /// /// /// /// /// /// /// /// /// /// Returns the transform of the object that was hit. That doesn't really belong in this function, but at least is uses one raycast less. public static Transform GetWorldToTextureSpaceMatrix(Vector3 pos, Vector3 rayDir, float maxDistance, float offset, LayerMask layerMask, out Vector2 texCoord, out Vector2 forward, out Vector2 right) { Ray ray = new Ray(pos, rayDir.normalized); RaycastHit hit; Debug.DrawRay(ray.origin, ray.direction * maxDistance, Color.red); if (Physics.Raycast(ray, out hit, maxDistance, layerMask)) { texCoord = hit.textureCoord; forward = GetTexCoordDifference(hit.transform, pos, texCoord, Vector3.forward * offset, maxDistance, layerMask); right = GetTexCoordDifference(hit.transform, pos, texCoord, Vector3.right * offset, maxDistance, layerMask); return hit.transform; } texCoord = Vector2.zero; forward = Vector2.zero; right = Vector2.zero; return null; } public static bool GetWorldToTextureSpaceMatrix(Ray ray, float offset, Collider[] colliders, out RaycastHit hit, out Vector2 forward, out Vector2 right) { var newHit = new RaycastHit(); if (colliders.Any(x => x.Raycast(ray, out newHit, Mathf.Infinity))) { forward = GetTexCoordDifference(ray.origin, newHit.textureCoord, Vector3.forward * offset, colliders); right = GetTexCoordDifference(ray.origin, newHit.textureCoord, Vector3.right * offset, colliders); hit = newHit; return true; } hit = newHit; forward = Vector2.zero; right = Vector2.zero; return false; } public static bool GetWorldToTextureSpaceMatrix(Ray ray, float offset, Collider collider, out RaycastHit hit, out Vector2 forward, out Vector2 right) { if (collider.Raycast(ray, out hit, Mathf.Infinity)) { forward = GetTexCoordDifference(ray.origin, hit.textureCoord, Vector3.forward * offset, collider); right = GetTexCoordDifference(ray.origin, hit.textureCoord, Vector3.right * offset, collider); return true; } hit = new RaycastHit(); forward = Vector2.zero; right = Vector2.zero; return false; } private static Vector2 GetTexCoordDifference(Transform targetObject, Vector3 pos, Vector2 texCoords, Vector3 offset, float maxDistance, LayerMask layerMask) { Ray ray = new Ray(pos + offset, Vector3.down); RaycastHit hit; //Send ray in dir to check for if (Physics.Raycast(ray, out hit, maxDistance, layerMask) && hit.transform == targetObject) { return (hit.textureCoord - texCoords) / offset.magnitude; } ray.direction = -ray.direction; if (Physics.Raycast(ray, out hit, maxDistance, layerMask) && hit.transform == targetObject) { return (texCoords - hit.textureCoord) / offset.magnitude; } return Vector2.zero; } private static Vector2 GetTexCoordDifference(Vector3 pos, Vector2 texCoords, Vector3 offset, Collider[] colliders) { Ray ray = new Ray(pos + offset, Vector3.down); RaycastHit hit = new RaycastHit(); //Send ray in dir to check for if (colliders.Any(x => x.Raycast(ray, out hit, Mathf.Infinity))) { return (hit.textureCoord - texCoords) / offset.magnitude; } ray.direction = -ray.direction; if (colliders.Any(x => x.Raycast(ray, out hit, Mathf.Infinity))) { return (texCoords - hit.textureCoord) / offset.magnitude; } return Vector2.zero; } private static Vector2 GetTexCoordDifference(Vector3 pos, Vector2 texCoords, Vector3 offset, Collider collider) { Ray ray = new Ray(pos + offset, Vector3.down); RaycastHit hit; //Send ray in dir to check for if (collider.Raycast(ray, out hit, Mathf.Infinity)) { return (hit.textureCoord - texCoords) / offset.magnitude; } ray.direction = -ray.direction; if (collider.Raycast(ray, out hit, Mathf.Infinity)) { return (texCoords - hit.textureCoord) / offset.magnitude; } return Vector2.zero; } /// /// Converts a vector to a world space normal map color. /// /// /// public static Vector2 VectorToColorSpace(Vector2 vec) { return (vec + Vector2.one) * 0.5f; } /// /// Converts a world space normal map color (vector) to a world space vector. /// /// /// public static Vector2 ColorToVectorSpace(Vector2 vec) { return vec * 2 - Vector2.one; } } }