Skip to content

Instantly share code, notes, and snippets.

@Thaina
Last active December 4, 2023 03:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Thaina/c4cea4f108da718924558fb2f7ff66df to your computer and use it in GitHub Desktop.
Save Thaina/c4cea4f108da718924558fb2f7ff66df to your computer and use it in GitHub Desktop.
Unity camera code for calculate PerspectiveOffCenter. Use parent object of camera as screen and camera local position as perspective offset
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Android;
using Mediapipe.Unity;
using Mediapipe.Unity.IrisTracking;
using TMPro;
using System.Linq;
public class CameraController : MonoBehaviour
{
public IrisTrackingGraph irisTrackingGraph;
public Transform camera;
public TMP_Text text;
public Vector2 leftEyeSize,rightEyeSize;
IEnumerator Start()
{
Debug.Log("Start");
Permission.RequestUserPermission(Permission.Camera);
while(!(ImageSourceProvider.ImageSource is var source && source && source.isPlaying))
yield return new WaitForEndOfFrame();
Debug.LogFormat("ImageSourceProvider.ImageSource : {0}",ImageSourceProvider.ImageSource);
while(irisTrackingGraph._faceLandmarksWithIrisStream == null)
yield return new WaitForEndOfFrame();
Debug.LogFormat("irisTrackingGraph : {0}",irisTrackingGraph._faceLandmarksWithIrisStream);
irisTrackingGraph._faceLandmarksWithIrisStream.AddListener((sender,output) => {
if(output?.value?.Landmark == null)
{
Debug.LogError("No landmark detected");
return;
}
var parts = FaceLandmarkListWithIrisAnnotation.PartitionLandmarkList(output.value.Landmark);
if(parts.leftIris == null && parts.rightIris == null)
{
Debug.LogError("No iris detected");
return;
}
leftEyeSize = new Vector2(parts.leftIris.Distance(1,3).Value,parts.leftIris.Distance(2,4).Value);
rightEyeSize = new Vector2(parts.rightIris.Distance(1,3).Value,parts.rightIris.Distance(2,4).Value);
});
}
WebCamSource webCamSource;
void Update()
{
if(!webCamSource)
webCamSource = GameObject.FindFirstObjectByType<WebCamSource>();
if(text && RelativeOffcenter.cameraCharacteristics?.Length > 0 && webCamSource?.webCamDevice?.isFrontFacing is bool facing)
{
var preferFacing = facing ? RelativeOffcenter.PreferFacing.Front : RelativeOffcenter.PreferFacing.Back;
var cc = RelativeOffcenter.cameraCharacteristics.OrderBy((characteristics) => characteristics.preferFacing == preferFacing ? 0 : 1).First();
var eyeSize = 11.75f * Vector2.one;
var dl = cc.DistanceMM(0,leftEyeSize * cc.SensorSizePx,eyeSize) / 1000;
var dr = cc.DistanceMM(0,rightEyeSize * cc.SensorSizePx,eyeSize) / 1000;
text.text = string.Join("\n", "L : " + leftEyeSize.ToString("0.0000"),"R : " + rightEyeSize.ToString("0.0000"),"DL : " + dl.ToString("0.0000"),"DR : " + dr.ToString("0.0000"));
}
}
}
using System;
using System.Linq;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
using TMPro;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
#if UNITY_ANDROID
using UnityEngine.Android;
#endif
[ExecuteInEditMode]
public class RelativeOffcenter : MonoBehaviour
{
public static Vector2 monitorSizeM;
#if UNITY_EDITOR
[UnityEditor.InitializeOnLoadMethod]
#endif
[RuntimeInitializeOnLoadMethod]
static void Init()
{
JsonConvert.DefaultSettings = () => {
var serializer = new JsonSerializerSettings();
serializer.Converters.Add(new VectorConverter());
return serializer;
};
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
var hwnd = GetDesktopWindow();
var hdc = GetDC(hwnd);
monitorSizeM.x = GetDeviceCaps(hdc,HORZSIZE) / 1000f;
monitorSizeM.y = GetDeviceCaps(hdc,VERTSIZE) / 1000f;
ReleaseDC(hwnd,hdc);
cameraCharacteristics = new [] {
new CameraCharacteristics() {
FocalLengths = new float[] { 2.4f },
SensorSizeMM = new Vector2(3.2128f,2.4128f),
SensorSizePx = new Vector2Int(4016,3016),
}
};
#elif UNITY_ANDROID
var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
var activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
var context = activity.Call<AndroidJavaObject>("getApplicationContext");
var displayMetrics = activity.Call<AndroidJavaObject>("getResources").Call<AndroidJavaObject>("getDisplayMetrics");
var dpi = new Vector2(displayMetrics.Get<float>("xdpi"),displayMetrics.Get<float>("ydpi"));
var pixels = new Vector2(displayMetrics.Get<int>("widthPixels"),displayMetrics.Get<int>("heightPixels"));
monitorSizeM = pixels * 25.4f / (dpi * 1000);
var cameraManager = context.Call<AndroidJavaObject>("getSystemService",context.GetStatic<string>("CAMERA_SERVICE"));
Debug.LogFormat("cameraManager : {0}",cameraManager);
var cameraIDs = cameraManager.Call<string[]>("getCameraIdList");
Debug.LogFormat("cameraIDs : {0}",string.Join(" | ",cameraIDs));
cameraCharacteristics = cameraIDs.Select((cameraID) => CameraCharacteristics.FromCameraID(cameraManager,cameraID)).ToArray();
#else
monitorSizeM = new Vector2(Screen.currentResolution.width,Screen.currentResolution.height) * 25.4f / (Screen.dpi * 1000);
#endif
}
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
const int HORZSIZE = 4;
const int VERTSIZE = 6;
[DllImport("gdi32.dll")]
static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
[DllImport("user32.dll", SetLastError = false)]
static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll", SetLastError = false)]
static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("user32.dll", SetLastError = false)]
static extern int ReleaseDC(IntPtr hwnd,IntPtr hdc);
#endif
public enum PreferFacing { Front = 0,Back = 1,None = 2 }
public struct CameraCharacteristics
{
public string CamerID;
public PreferFacing preferFacing;
public Vector2Int SensorSizePx;
public Vector2 SensorSizeMM;
public Vector3 LensPosition;
public Quaternion LensRotation;
public float[] FocalLengths;
public Vector2 PixelToMM(Vector2 px) => px * SensorSizeMM / SensorSizePx;
public Vector2 DistanceMM(int findex,Vector2 px,Vector2 realSize) => DistanceMM(FocalLengths[findex],PixelToMM(px),realSize);
public static float DistanceMM(in float focalLength,in float lensSizeMM,in float realSizeMM)
{
return focalLength + (focalLength * realSizeMM / lensSizeMM);
}
public static Vector2 DistanceMM(in float focalLength,in Vector2 lensSizeMM,in Vector2 realSizeMM)
{
return new Vector2(DistanceMM(focalLength,lensSizeMM.x,realSizeMM.x),DistanceMM(focalLength,lensSizeMM.y,realSizeMM.y));
}
#if UNITY_ANDROID
public static CameraCharacteristics FromCameraID(AndroidJavaObject cameraManager,string cameraID)
{
var CameraCharacteristics = cameraManager.Call<AndroidJavaObject>("getCameraCharacteristics",cameraID);
CameraCharacteristics data;
data.CamerID = cameraID;
data.preferFacing = (PreferFacing)CameraCharacteristics.GetFromConstantName<AndroidJavaObject>("LENS_FACING").Call<int>("intValue");
data.FocalLengths = CameraCharacteristics.GetFromConstantName<float[]>("LENS_INFO_AVAILABLE_FOCAL_LENGTHS");
data.SensorSizeMM = CameraCharacteristics.GetFromConstantName<AndroidJavaObject>("SENSOR_INFO_PHYSICAL_SIZE").ConvertFromSizeF();
data.SensorSizePx = CameraCharacteristics.GetFromConstantName<AndroidJavaObject>("SENSOR_INFO_PIXEL_ARRAY_SIZE").ConvertFromSize();
data.LensPosition = CameraCharacteristics.GetFromConstantName<float[]>("LENS_POSE_TRANSLATION").ReadVector3();
data.LensRotation = CameraCharacteristics.GetFromConstantName<float[]>("LENS_POSE_ROTATION").ReadQuaternion();
return data;
}
#endif
}
public TMP_Text text;
public static CameraCharacteristics[] cameraCharacteristics;
void Start()
{
text.text = JArray.FromObject(cameraCharacteristics).ToString(Formatting.Indented);
}
public class VectorConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Vector2) || objectType == typeof(Vector3) || objectType == typeof(Vector4) || objectType == typeof(Quaternion);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var t = serializer.Deserialize(reader);
return JsonConvert.DeserializeObject(t.ToString(),objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if(value is Vector2 v2)
{
writer.WriteStartObject();
writer.WritePropertyName("x");
writer.WriteValue(v2.x);
writer.WritePropertyName("y");
writer.WriteValue(v2.y);
writer.WriteEndObject();
}
else if(value is Vector3 v3)
{
writer.WriteStartObject();
writer.WritePropertyName("x");
writer.WriteValue(v3.x);
writer.WritePropertyName("y");
writer.WriteValue(v3.y);
writer.WritePropertyName("z");
writer.WriteValue(v3.z);
writer.WriteEndObject();
}
else if(value is Vector4 v4)
{
writer.WriteStartObject();
writer.WritePropertyName("x");
writer.WriteValue(v4.x);
writer.WritePropertyName("y");
writer.WriteValue(v4.y);
writer.WritePropertyName("z");
writer.WriteValue(v4.z);
writer.WritePropertyName("w");
writer.WriteValue(v4.w);
writer.WriteEndObject();
}
else if(value is Quaternion q)
{
writer.WriteStartObject();
writer.WritePropertyName("x");
writer.WriteValue(q.x);
writer.WritePropertyName("y");
writer.WriteValue(q.y);
writer.WritePropertyName("z");
writer.WriteValue(q.z);
writer.WritePropertyName("w");
writer.WriteValue(q.w);
writer.WriteEndObject();
}
}
}
Rect rect;
Vector3 center;
new Camera camera;
void LateUpdate()
{
if(!camera)
camera = gameObject.GetComponent<Camera>();
var focus = gameObject.transform.parent ? gameObject.transform.parent.position : Vector3.zero;
var camLoc = camera.gameObject.transform;
var ray = new Ray(camLoc.position,camLoc.forward);
camera.nearClipPlane = Vector3.Dot(ray.direction,focus - ray.origin);
center = focus - (ray.direction * camera.nearClipPlane);
var monitorSizePixels = new Vector2(Screen.currentResolution.width,Screen.currentResolution.height);
var monitorRealScale = monitorSizeM / monitorSizePixels;
var monitorCenter = monitorSizePixels / 2;
#if UNITY_EDITOR
if(Screen.mainWindowPosition.sqrMagnitude > 0)
rect = new Rect(Screen.mainWindowPosition,new Vector2(Screen.width,Screen.height));
else rect = UnityEditor.SceneView.lastActiveSceneView.position;
#else
rect = new Rect(Screen.mainWindowPosition,new Vector2(Screen.width,Screen.height));
#endif
rect.min = monitorRealScale * (rect.min - monitorCenter);
rect.max = rect.min + (monitorRealScale * new Vector2(Screen.width,Screen.height));
var frame = rect;
frame.position -= (Vector2)gameObject.transform.localPosition;
camera.projectionMatrix = Unity.Mathematics.float4x4.PerspectiveOffCenter(frame.xMin,frame.xMax,-frame.yMax,-frame.yMin,camera.nearClipPlane,camera.farClipPlane);
}
#if UNITY_EDITOR
void OnDrawGizmos()
{
if(!camera)
camera = gameObject.GetComponent<Camera>();
var offset = center + (camera.gameObject.transform.forward * camera.nearClipPlane);
Gizmos.color = Color.green;
foreach(var (from,to) in PointLoopToLine(TransformRectPointFromMinMax(gameObject.transform,new Rect(-0.5f * monitorSizeM,monitorSizeM))))
Gizmos.DrawLine(offset + from,offset + to);
Gizmos.color = Color.red;
foreach(var (from,to) in PointLoopToLine(TransformRectPointFromMinMax(gameObject.transform,rect)))
Gizmos.DrawLine(offset + from,offset + to);
static IEnumerable<Vector3> TransformRectPointFromMinMax(Transform transform,Rect rect)
{
var up = transform.up;
var right = transform.right;
yield return (right * rect.xMin) - (up * rect.yMin);
yield return (right * rect.xMax) - (up * rect.yMin);
yield return (right * rect.xMax) - (up * rect.yMax);
yield return (right * rect.xMin) - (up * rect.yMax);
}
static IEnumerable<(Vector3,Vector3)> PointLoopToLine(IEnumerable<Vector3> points)
{
var itor = points.GetEnumerator();
if(!itor.MoveNext())
yield break;
var first = itor.Current;
if(!itor.MoveNext())
yield break;
var prev = first;
do
{
var current = itor.Current;
yield return (prev,current);
prev = current;
}
while(itor.MoveNext());
yield return (prev,first);
}
}
#endif
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment