From e608e2a56bdd9920bc59d4c0f62e3f576f2fabc2 Mon Sep 17 00:00:00 2001 From: Lillith Rose Date: Thu, 4 Dec 2025 19:16:38 -0500 Subject: [PATCH] Camera tools --- CamToImage.meta | 8 ++ CamToImage/Runtime.meta | 8 ++ CamToImage/Runtime/CamToImage.asmdef | 14 +++ CamToImage/Runtime/CamToImage.asmdef.meta | 7 ++ CamToImage/Runtime/CamToImage.cs | 99 +++++++++++++++ CamToImage/Runtime/CamToImage.cs.meta | 11 ++ PlaneCam.meta | 8 ++ PlaneCam/Editor.meta | 8 ++ PlaneCam/Editor/PlaneCamEditor.asmdef | 18 +++ PlaneCam/Editor/PlaneCamEditor.asmdef.meta | 7 ++ PlaneCam/Editor/PlaneCamEditor.cs | 121 +++++++++++++++++++ PlaneCam/Editor/PlaneCamEditor.cs.meta | 11 ++ PlaneCam/Runtime.meta | 8 ++ PlaneCam/Runtime/PlaneCam.cs | 24 ++++ PlaneCam/Runtime/PlaneCam.cs.meta | 11 ++ PlaneCam/Runtime/PlaneCamRuntime.asmdef | 14 +++ PlaneCam/Runtime/PlaneCamRuntime.asmdef.meta | 7 ++ 17 files changed, 384 insertions(+) create mode 100644 CamToImage.meta create mode 100644 CamToImage/Runtime.meta create mode 100644 CamToImage/Runtime/CamToImage.asmdef create mode 100644 CamToImage/Runtime/CamToImage.asmdef.meta create mode 100644 CamToImage/Runtime/CamToImage.cs create mode 100644 CamToImage/Runtime/CamToImage.cs.meta create mode 100644 PlaneCam.meta create mode 100644 PlaneCam/Editor.meta create mode 100644 PlaneCam/Editor/PlaneCamEditor.asmdef create mode 100644 PlaneCam/Editor/PlaneCamEditor.asmdef.meta create mode 100644 PlaneCam/Editor/PlaneCamEditor.cs create mode 100644 PlaneCam/Editor/PlaneCamEditor.cs.meta create mode 100644 PlaneCam/Runtime.meta create mode 100644 PlaneCam/Runtime/PlaneCam.cs create mode 100644 PlaneCam/Runtime/PlaneCam.cs.meta create mode 100644 PlaneCam/Runtime/PlaneCamRuntime.asmdef create mode 100644 PlaneCam/Runtime/PlaneCamRuntime.asmdef.meta diff --git a/CamToImage.meta b/CamToImage.meta new file mode 100644 index 0000000..e33cc3b --- /dev/null +++ b/CamToImage.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e913617f0585d954799673a0cba0b987 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/CamToImage/Runtime.meta b/CamToImage/Runtime.meta new file mode 100644 index 0000000..3427bae --- /dev/null +++ b/CamToImage/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 14ddfe4ade1ec0945b8355ace2110882 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/CamToImage/Runtime/CamToImage.asmdef b/CamToImage/Runtime/CamToImage.asmdef new file mode 100644 index 0000000..5868d2c --- /dev/null +++ b/CamToImage/Runtime/CamToImage.asmdef @@ -0,0 +1,14 @@ +{ + "name": "CamToImage", + "rootNamespace": "", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/CamToImage/Runtime/CamToImage.asmdef.meta b/CamToImage/Runtime/CamToImage.asmdef.meta new file mode 100644 index 0000000..efbe580 --- /dev/null +++ b/CamToImage/Runtime/CamToImage.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 32d7f771e78b8bf41b2d888d99ac3867 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/CamToImage/Runtime/CamToImage.cs b/CamToImage/Runtime/CamToImage.cs new file mode 100644 index 0000000..90025a1 --- /dev/null +++ b/CamToImage/Runtime/CamToImage.cs @@ -0,0 +1,99 @@ + +using System.IO; +using UnityEngine; + + +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace gay.lilyy.PlaneCam +{ + [AddComponentMenu("LillithRosePup/Camera To Image")] + [RequireComponent(typeof(Camera))] + public class PlaneCam : MonoBehaviour, VRC.SDKBase.IEditorOnly + { + + private const string DefaultOutputImagePath = "Output.png"; + public string outputImagePath = DefaultOutputImagePath; + + [Tooltip("If set to a value greater than 0, the image will be scaled so its longest side matches this value while maintaining aspect ratio")] + public int longestSide = 1024; + } + + #if UNITY_EDITOR + [CustomEditor(typeof(PlaneCam))] + public class PlaneCamEditor : Editor + { + public override void OnInspectorGUI() + { + base.OnInspectorGUI(); + if (GUILayout.Button("Capture Image")) + { + var camera = target as PlaneCam; + + } + } + + private void CaptureImage(PlaneCam camDef) { + var camera = camDef.GetComponent(); + + float aspectRatio = camera.aspect; + + int cameraWidth = camera.pixelWidth; + int cameraHeight = camera.pixelHeight; + + int outputWidth, outputHeight; + if (aspectRatio > 1f) + { + outputHeight = cameraHeight; + outputWidth = Mathf.RoundToInt(cameraHeight * aspectRatio); + if (outputWidth > cameraWidth) + { + outputWidth = cameraWidth; + outputHeight = Mathf.RoundToInt(cameraWidth / aspectRatio); + } + } + else + { + outputWidth = cameraWidth; + outputHeight = Mathf.RoundToInt(cameraWidth / aspectRatio); + if (outputHeight > cameraHeight) + { + outputHeight = cameraHeight; + outputWidth = Mathf.RoundToInt(cameraHeight * aspectRatio); + } + } + + // Scale to longest side if specified + if (camDef.longestSide > 0) + { + int currentLongestSide = Mathf.Max(outputWidth, outputHeight); + if (currentLongestSide != camDef.longestSide) + { + float scale = (float)camDef.longestSide / currentLongestSide; + outputWidth = Mathf.RoundToInt(outputWidth * scale); + outputHeight = Mathf.RoundToInt(outputHeight * scale); + } + } + + var renderTexture = new RenderTexture(outputWidth, outputHeight, 24); + camera.targetTexture = renderTexture; + camera.Render(); + + var screenshot = new Texture2D(outputWidth, outputHeight, TextureFormat.ARGB32, false); + RenderTexture.active = renderTexture; + screenshot.ReadPixels(new Rect(0, 0, outputWidth, outputHeight), 0, 0); + screenshot.Apply(); + RenderTexture.active = null; + camera.targetTexture = null; + renderTexture.Release(); + + var bytes = screenshot.EncodeToPNG(); + File.WriteAllBytes("Assets/" + camDef.outputImagePath, bytes); + AssetDatabase.Refresh(); + Debug.Log($"Image captured and saved to: Assets/{camDef.outputImagePath}. Aspect Ratio: {aspectRatio:F2} ({outputWidth}x{outputHeight})"); + } + } + #endif +} \ No newline at end of file diff --git a/CamToImage/Runtime/CamToImage.cs.meta b/CamToImage/Runtime/CamToImage.cs.meta new file mode 100644 index 0000000..c5e3749 --- /dev/null +++ b/CamToImage/Runtime/CamToImage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 007ac06a66da7cb4b86fde9e783da04d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/PlaneCam.meta b/PlaneCam.meta new file mode 100644 index 0000000..2bfe5e3 --- /dev/null +++ b/PlaneCam.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d1a8dda0420e5fa43bf7d6497beec37a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/PlaneCam/Editor.meta b/PlaneCam/Editor.meta new file mode 100644 index 0000000..a878193 --- /dev/null +++ b/PlaneCam/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dfb718cc85f807a4585cc3dd07098767 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/PlaneCam/Editor/PlaneCamEditor.asmdef b/PlaneCam/Editor/PlaneCamEditor.asmdef new file mode 100644 index 0000000..cad9b23 --- /dev/null +++ b/PlaneCam/Editor/PlaneCamEditor.asmdef @@ -0,0 +1,18 @@ +{ + "name": "PlaneCamEditor", + "rootNamespace": "", + "references": [ + "GUID:a9f136790ce90f740a7142ab21ff971a" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/PlaneCam/Editor/PlaneCamEditor.asmdef.meta b/PlaneCam/Editor/PlaneCamEditor.asmdef.meta new file mode 100644 index 0000000..4938459 --- /dev/null +++ b/PlaneCam/Editor/PlaneCamEditor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0010e90dfaa354f4ca094b433acef847 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/PlaneCam/Editor/PlaneCamEditor.cs b/PlaneCam/Editor/PlaneCamEditor.cs new file mode 100644 index 0000000..ba95141 --- /dev/null +++ b/PlaneCam/Editor/PlaneCamEditor.cs @@ -0,0 +1,121 @@ +using UnityEngine; +using UnityEditor; +using gay.lilyy.PlaneCam; + +namespace gay.lilyy.PlaneCam.Editor +{ + [InitializeOnLoad] + public class PlaneCamEditor + { + static PlaneCamEditor() + { + EditorApplication.update += UpdatePlaneCams; + } + + static void UpdatePlaneCams() + { + PlaneCam[] planeCams = Object.FindObjectsOfType(true); + + foreach (PlaneCam planeCam in planeCams) + { + if (planeCam == null || planeCam.target == null) + continue; + + Camera cam = planeCam.GetComponent(); + if (cam == null) + continue; + + // Get the bounds of the target plane + Bounds bounds = GetBounds(planeCam.target); + + if (bounds.size.magnitude < 0.001f) + continue; + + // Get plane normal and center + Vector3 planeNormal = GetPlaneNormal(planeCam.target, planeCam.direction); + Vector3 planeCenter = bounds.center; + + // Calculate the plane's dimensions in its local space + // Project bounds size onto the plane's local axes + Transform planeTransform = planeCam.target.transform; + Vector3 localSize = bounds.size; + + // Find the two dimensions that define the plane (ignore the smallest dimension) + // This assumes the plane is flat, so one dimension should be very small + float minDim = Mathf.Min(localSize.x, localSize.y, localSize.z); + float maxDim = Mathf.Max(localSize.x, localSize.y, localSize.z); + float midDim = localSize.x + localSize.y + localSize.z - minDim - maxDim; + + // Use the larger of the two plane dimensions to ensure it fills the square viewport + float planeSize = Mathf.Max(maxDim, midDim); + + // Set aspect ratio to 1:1 for square viewport + cam.aspect = 1.0f; + + // Calculate distance and position + if (cam.orthographic) + { + cam.orthographicSize = planeSize * 0.5f; + // Position camera perpendicular to the plane + cam.transform.position = planeCenter - planeNormal * 10f; // Distance doesn't matter for orthographic + cam.transform.LookAt(planeCenter, planeTransform.up); + } + else + { + // For perspective camera, calculate distance based on FOV + // distance = (size/2) / tan(FOV/2) + float halfFOV = cam.fieldOfView * 0.5f * Mathf.Deg2Rad; + float distance = (planeSize * 0.5f) / Mathf.Tan(halfFOV); + + // Position camera perpendicular to the plane, looking at center + cam.transform.position = planeCenter - planeNormal * distance; + cam.transform.LookAt(planeCenter, planeTransform.up); + } + } + } + + static Bounds GetBounds(GameObject target) + { + Renderer renderer = target.GetComponent(); + if (renderer != null) + { + return renderer.bounds; + } + + // If no renderer, try to get bounds from collider + Collider collider = target.GetComponent(); + if (collider != null) + { + return collider.bounds; + } + + // Fallback: use transform scale + return new Bounds(target.transform.position, target.transform.lossyScale); + } + + static Vector3 GetPlaneNormal(GameObject target, PlaneDirection direction) + { + Transform t = target.transform; + + // Use the specified direction setting + switch (direction) + { + case PlaneDirection.XPositive: + return t.right; + case PlaneDirection.XNegative: + return -t.right; + case PlaneDirection.YPositive: + return t.up; + case PlaneDirection.YNegative: + return -t.up; + case PlaneDirection.ZPositive: + return t.forward; + case PlaneDirection.ZNegative: + return -t.forward; + default: + return t.forward; + } + } + } +} + diff --git a/PlaneCam/Editor/PlaneCamEditor.cs.meta b/PlaneCam/Editor/PlaneCamEditor.cs.meta new file mode 100644 index 0000000..401f28d --- /dev/null +++ b/PlaneCam/Editor/PlaneCamEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0e148c194888c8f4b80fbb649b2f1a02 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/PlaneCam/Runtime.meta b/PlaneCam/Runtime.meta new file mode 100644 index 0000000..16a7ba3 --- /dev/null +++ b/PlaneCam/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a6bc6863465f2e64db8b50aa3a1d067f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/PlaneCam/Runtime/PlaneCam.cs b/PlaneCam/Runtime/PlaneCam.cs new file mode 100644 index 0000000..650a87c --- /dev/null +++ b/PlaneCam/Runtime/PlaneCam.cs @@ -0,0 +1,24 @@ + +using UnityEngine; + + +namespace gay.lilyy.PlaneCam +{ + public enum PlaneDirection + { + XPositive, + XNegative, + YPositive, + YNegative, + ZPositive, + ZNegative + } + + [AddComponentMenu("LillithRosePup/Plane Cam")] + [RequireComponent(typeof(Camera))] + public class PlaneCam : MonoBehaviour, VRC.SDKBase.IEditorOnly + { + public GameObject target; + public PlaneDirection direction = PlaneDirection.ZPositive; + } +} \ No newline at end of file diff --git a/PlaneCam/Runtime/PlaneCam.cs.meta b/PlaneCam/Runtime/PlaneCam.cs.meta new file mode 100644 index 0000000..2298e6b --- /dev/null +++ b/PlaneCam/Runtime/PlaneCam.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 49ab2f404b9727b4dadf40b4aea753d0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/PlaneCam/Runtime/PlaneCamRuntime.asmdef b/PlaneCam/Runtime/PlaneCamRuntime.asmdef new file mode 100644 index 0000000..8eff451 --- /dev/null +++ b/PlaneCam/Runtime/PlaneCamRuntime.asmdef @@ -0,0 +1,14 @@ +{ + "name": "PlaneCamRuntime", + "rootNamespace": "", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/PlaneCam/Runtime/PlaneCamRuntime.asmdef.meta b/PlaneCam/Runtime/PlaneCamRuntime.asmdef.meta new file mode 100644 index 0000000..8bc4868 --- /dev/null +++ b/PlaneCam/Runtime/PlaneCamRuntime.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a9f136790ce90f740a7142ab21ff971a +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: