summaryrefslogtreecommitdiffstats
path: root/Scripts
diff options
context:
space:
mode:
Diffstat (limited to 'Scripts')
-rw-r--r--Scripts/Impostors.cs240
1 files changed, 240 insertions, 0 deletions
diff --git a/Scripts/Impostors.cs b/Scripts/Impostors.cs
new file mode 100644
index 0000000..e10daf1
--- /dev/null
+++ b/Scripts/Impostors.cs
@@ -0,0 +1,240 @@
+using UnityEngine;
+using UnityEditor;
+using System.IO;
+
+[ExecuteInEditMode]
+public class Impostors : MonoBehaviour
+{
+ [Header("Bounding Sphere")]
+ [Tooltip("Sphere radius is controlled by the Transform scale (uses X component)")]
+ public float sphere_radius_ = 1f;
+
+ [Header("Grid Settings")]
+ [Tooltip("Number of lattice points along each axis (e.g., 5 = 5x5 = 25 points)")]
+ [Range(2, 20)]
+ public int gridResolution = 5;
+
+ [Header("Camera Settings")]
+ [Range(1, 4096)]
+ public int cameraResolution = 256;
+
+ [Tooltip("Distance multiplier from sphere center for camera placement")]
+ public float cameraDistance = 2f;
+
+ [HideInInspector]
+ public GameObject[] cameraObjects;
+ [HideInInspector]
+ public Camera[] cameras;
+
+ private float radius() { return sphere_radius_ * transform.localScale.x; }
+
+ void OnDrawGizmos()
+ {
+ // Use transform scale directly for real-time gizmo updates
+ float currentRadius = radius();
+
+ // Draw the bounding sphere
+ Gizmos.color = Color.cyan;
+ Gizmos.DrawWireSphere(transform.position, currentRadius);
+
+ // Draw the camera positions and directions
+ if (Application.isEditor && gridResolution > 0)
+ {
+ Gizmos.color = Color.yellow;
+
+ for (int y = 0; y < gridResolution; y++)
+ {
+ for (int x = 0; x < gridResolution; x++)
+ {
+ Vector3 hemispherePos = PlaneToHemiOctahedron(x, y);
+ Vector3 worldPos = transform.position + hemispherePos * currentRadius * cameraDistance;
+
+ // Draw camera position
+ Gizmos.DrawSphere(worldPos, currentRadius * 0.05f);
+
+ // Draw line to sphere center
+ Gizmos.DrawLine(worldPos, transform.position);
+ }
+ }
+ }
+ }
+
+ // Port of plane_to_hemi_octahedron from vertex_deformation.slang
+ Vector3 PlaneToHemiOctahedron(int gridX, int gridY)
+ {
+ // Map grid indices to [-1, 1] plane coordinates
+ float u = (gridX / (float)(gridResolution - 1)) * 2f - 1f;
+ float v = (gridY / (float)(gridResolution - 1)) * 2f - 1f;
+
+ float x = u;
+ float z = v;
+
+ // Rotate 45° and scale to fit square into diamond
+ float x_rot = (x + z) * 0.5f;
+ float z_rot = (z - x) * 0.5f;
+
+ // Octahedral decode: y = 1 - |x| - |z|, clamped to hemisphere
+ float y = Mathf.Max(0f, 1f - Mathf.Abs(x_rot) - Mathf.Abs(z_rot));
+
+ // Normalize to unit sphere
+ Vector3 oct_pos = new Vector3(x_rot, y, z_rot);
+ oct_pos.Normalize();
+
+ // Rotate back by -45° around y to undo input rotation
+ float RCP_SQRT_2 = 0.70710678f;
+ float x_unrot = (oct_pos.x - oct_pos.z) * RCP_SQRT_2;
+ float z_unrot = (oct_pos.x + oct_pos.z) * RCP_SQRT_2;
+ oct_pos = new Vector3(x_unrot, oct_pos.y, z_unrot);
+
+ return oct_pos;
+ }
+
+ public void CreateCameras()
+ {
+ // Clean up existing cameras
+ DestroyExistingCameras();
+
+ int totalCameras = gridResolution * gridResolution;
+ cameraObjects = new GameObject[totalCameras];
+ cameras = new Camera[totalCameras];
+
+ int index = 0;
+ for (int y = 0; y < gridResolution; y++)
+ {
+ for (int x = 0; x < gridResolution; x++)
+ {
+ // Get position on hemisphere
+ Vector3 hemisphereDir = PlaneToHemiOctahedron(x, y);
+ float currentRadius = radius();
+ Vector3 worldPos = transform.position + hemisphereDir * currentRadius * cameraDistance;
+
+ // Create camera GameObject
+ GameObject camObj = new GameObject($"ImpostorCamera_{x}_{y}");
+ camObj.transform.parent = transform;
+ camObj.transform.position = worldPos;
+ camObj.transform.LookAt(transform.position);
+
+ // Add and configure camera
+ Camera cam = camObj.AddComponent<Camera>();
+ cam.orthographic = true;
+ cam.orthographicSize = currentRadius;
+ cam.nearClipPlane = 0.01f;
+ cam.farClipPlane = currentRadius * cameraDistance * 2f;
+ cam.enabled = false; // Only enable during baking
+
+ cameraObjects[index] = camObj;
+ cameras[index] = cam;
+ index++;
+ }
+ }
+
+ Debug.Log($"Created {totalCameras} impostor cameras");
+ }
+
+ public void DestroyExistingCameras()
+ {
+ // Find and destroy all children with "ImpostorCamera" in name
+ for (int i = transform.childCount - 1; i >= 0; i--)
+ {
+ Transform child = transform.GetChild(i);
+ if (child.name.StartsWith("ImpostorCamera"))
+ {
+ DestroyImmediate(child.gameObject);
+ }
+ }
+
+ cameraObjects = null;
+ cameras = null;
+ }
+
+ public void BakeTexture()
+ {
+ // Create a texture atlas to hold all camera views
+ int texWidth = cameraResolution * gridResolution;
+ int texHeight = cameraResolution * gridResolution;
+ Texture2D atlasTexture = new Texture2D(texWidth, texHeight, TextureFormat.RGBA32, false);
+
+ // Create temporary render texture for each camera
+ RenderTexture rt = RenderTexture.GetTemporary(cameraResolution, cameraResolution, 24);
+
+ int index = 0;
+ for (int y = 0; y < gridResolution; y++)
+ {
+ for (int x = 0; x < gridResolution; x++)
+ {
+ Camera cam = cameras[index];
+
+ // Render camera to RT
+ cam.targetTexture = rt;
+ cam.Render();
+
+ // Read pixels from RT
+ RenderTexture.active = rt;
+ Texture2D temp = new Texture2D(cameraResolution, cameraResolution, TextureFormat.RGBA32, false);
+ temp.ReadPixels(new Rect(0, 0, cameraResolution, cameraResolution), 0, 0);
+ temp.Apply();
+
+ // Copy to atlas at correct position
+ int atlasX = x * cameraResolution;
+ int atlasY = y * cameraResolution;
+ atlasTexture.SetPixels(atlasX, atlasY, cameraResolution, cameraResolution, temp.GetPixels());
+
+ DestroyImmediate(temp);
+ index++;
+ }
+ }
+
+ atlasTexture.Apply();
+ RenderTexture.active = null;
+ RenderTexture.ReleaseTemporary(rt);
+
+ // Save texture to file
+ byte[] bytes = atlasTexture.EncodeToPNG();
+ string path = Path.Combine(Application.dataPath, "yum_food/3ner/ho_bake.png");
+ File.WriteAllBytes(path, bytes);
+
+ Debug.Log($"Baked texture saved to: {path}");
+ Debug.Log($"Atlas size: {texWidth}x{texHeight} ({gridResolution}x{gridResolution} grid of {cameraResolution}x{cameraResolution} images)");
+
+ // Refresh asset database
+ AssetDatabase.Refresh();
+
+ DestroyImmediate(atlasTexture);
+ }
+}
+
+[CustomEditor(typeof(Impostors))]
+public class ImpostorsEditor : Editor
+{
+ public override void OnInspectorGUI()
+ {
+ DrawDefaultInspector();
+
+ Impostors script = (Impostors)target;
+
+ GUILayout.Space(10);
+
+ if (GUILayout.Button("Create Cameras", GUILayout.Height(30)))
+ {
+ script.CreateCameras();
+ }
+
+ if (GUILayout.Button("Destroy Cameras", GUILayout.Height(30)))
+ {
+ script.DestroyExistingCameras();
+ }
+
+ GUILayout.Space(10);
+
+ bool hasCameras = script.cameras != null && script.cameras.Length > 0;
+
+ GUI.enabled = hasCameras;
+ if (GUILayout.Button(new GUIContent("Bake Texture",
+ hasCameras ? "" : "Create cameras first"), GUILayout.Height(40)))
+ {
+ script.BakeTexture();
+ }
+ GUI.enabled = true;
+ }
+}
+