using System.Collections.Generic;
using BrewMonster.Scripts.Managers;
using UnityEngine;
namespace BrewMonster
{
///
/// Renders an EC_ElsePlayer portrait into a RenderTexture for the target HUD (HUDNPC).
///
/// Instead of following the live player transform every frame (which causes jitter when
/// the target moves/jumps), this system clones the visual model into an isolated
/// "portrait stage" at a fixed world position. The portrait camera only ever looks at
/// the static clone, so the output is perfectly stable.
///
/// Public API (unchanged — CECUIManager needs no edits):
/// SetTarget(Transform playerRoot) — clone model, start rendering
/// ClearTarget() — destroy clone, stop rendering
/// OutputTexture — wire to RawImage.texture
///
public class ElsePlayerPortraitCapture : MonoSingleton
{
[Header("Portrait Camera")]
[SerializeField] private Camera portraitCamera;
[Header("Render Output")]
[SerializeField] private RenderTexture outputTexture;
[Header("Portrait Stage")]
[Tooltip("Layer index for the Portrait layer (Project Settings > Tags and Layers).")]
[SerializeField] private int portraitLayer = 9;
[Tooltip("Fixed world position of the clone. Keep far from gameplay to avoid overlap.")]
[SerializeField] private Vector3 stagePosition = new Vector3(9999f, 0f, 0f);
[Header("Framing")]
[Tooltip("Distance the camera sits in front of the face (meters). ~0.5–1.0 for tight portrait.")]
[SerializeField] private float cameraDistance = 0.7f;
[Tooltip("Extra upward offset applied to the look-at focus point (moves portrait up toward eyes).")]
[SerializeField] private float faceUpLift = 0.04f;
[Tooltip("How fast the camera rotation interpolates toward the face each frame (higher = snappier).")]
[SerializeField][Range(1f, 50f)] private float lookSmoothSpeed = 20f;
[Header("Head Bone Face Direction")]
[Tooltip("Which local axis of Bip01 Head points OUT of the face (toward the camera).")]
[SerializeField] private FaceAxis headFaceAxis = FaceAxis.Forward;
[Tooltip("Flip the chosen face axis (e.g. if Forward gives back-of-head, enable this).")]
[SerializeField] private bool flipFaceAxis;
[SerializeField][Range(10f, 60f)] private float fieldOfView = 35f;
[Header("Materials")]
[Tooltip("Clone each renderer material on the portrait clone and switch copies to URP/Unlit.")]
[SerializeField] private bool usePortraitUnlitMaterials = true;
// ── runtime state ────────────────────────────────────────────────────────────
private GameObject _portraitClone;
private Transform _headBone;
private readonly List _portraitMaterialInstances = new();
public RenderTexture OutputTexture => outputTexture;
// ──────────────────────────────────────────────────────────────────────────────
// Lifecycle
// ──────────────────────────────────────────────────────────────────────────────
protected override void Initialize()
{
EnsureCamera();
EnsureRenderTexture();
}
protected override void OnDestroy()
{
base.OnDestroy();
ClearTarget();
if (outputTexture != null && outputTexture.IsCreated())
outputTexture.Release();
}
// ──────────────────────────────────────────────────────────────────────────────
// Public API
// ──────────────────────────────────────────────────────────────────────────────
///
/// Clones the visual model of into the portrait stage
/// and starts rendering. Safe to call multiple times — old clone is destroyed first.
///
public void SetTarget(Transform playerRoot)
{
ClearTarget();
if (playerRoot == null) return;
Transform modelRoot = PortraitCaptureUtils.FindVisualModelRoot(playerRoot);
if (modelRoot == null)
{
BMLogger.LogWarning("[ElsePlayerPortraitCapture] Visual model root not found — portrait skipped.");
return;
}
_portraitClone = Object.Instantiate(modelRoot.gameObject);
_portraitClone.GetComponentInChildren().enabled = false;
_portraitClone.name = "[Portrait] ElsePlayer";
_portraitClone.transform.position = stagePosition;
_portraitClone.transform.rotation = Quaternion.identity;
// Strip physics / game-logic components; keep Animator for idle animation
PortraitCaptureUtils.CleanupCloneComponents(_portraitClone);
// Hide from main camera — portrait camera sees only this layer
PortraitCaptureUtils.SetLayerRecursive(_portraitClone, portraitLayer);
if (usePortraitUnlitMaterials)
{
PortraitCaptureUtils.ApplyClonedUrpUnlitMaterials(
_portraitClone,
_portraitMaterialInstances,
static msg => BMLogger.LogWarning(msg));
}
_headBone = PortraitCaptureUtils.FindHeadBone(_portraitClone.transform);
AttachCameraToHeadBone();
SetCameraEnabled(true);
}
///
/// Destroys the portrait clone and disables the camera.
/// Call from OnTargetHUDClear / TryHideUINPC.
///
public void ClearTarget()
{
if (_portraitClone != null)
{
Object.Destroy(_portraitClone);
_portraitClone = null;
}
for (int i = 0; i < _portraitMaterialInstances.Count; i++)
{
if (_portraitMaterialInstances[i] != null)
Object.Destroy(_portraitMaterialInstances[i]);
}
_portraitMaterialInstances.Clear();
DetachCamera();
_headBone = null;
}
private void LateUpdate()
{
if (portraitCamera == null || !portraitCamera.enabled) return;
if (_headBone == null) return;
Vector3 faceCenter = _headBone.position + Vector3.up * faceUpLift;
Vector3 toFace = faceCenter - portraitCamera.transform.position;
if (toFace.sqrMagnitude < 0.0001f) return;
Quaternion targetRot = Quaternion.LookRotation(toFace);
portraitCamera.transform.rotation = Quaternion.Slerp(
portraitCamera.transform.rotation,
targetRot,
Time.deltaTime * lookSmoothSpeed);
}
// ──────────────────────────────────────────────────────────────────────────────
// Internal
// ──────────────────────────────────────────────────────────────────────────────
private void AttachCameraToHeadBone()
{
if (portraitCamera == null) return;
Transform anchor = _headBone != null ? _headBone : _portraitClone.transform;
Vector3 faceDir = GetFaceDirectionWorld(anchor);
Vector3 headPos = anchor.position;
Vector3 faceCenter = headPos + Vector3.up * faceUpLift;
Vector3 camWorldPos = headPos + faceDir * cameraDistance;
portraitCamera.transform.SetParent(anchor, worldPositionStays: false);
portraitCamera.transform.position = camWorldPos;
portraitCamera.transform.LookAt(faceCenter);
}
private Vector3 GetFaceDirectionWorld(Transform bone)
{
Vector3 dir = headFaceAxis switch
{
FaceAxis.Forward => bone.forward,
FaceAxis.Back => -bone.forward,
FaceAxis.Up => bone.up,
FaceAxis.Down => -bone.up,
FaceAxis.Right => bone.right,
FaceAxis.Left => -bone.right,
_ => bone.forward
};
return flipFaceAxis ? -dir : dir;
}
private void DetachCamera()
{
if (portraitCamera != null)
{
portraitCamera.transform.SetParent(transform, worldPositionStays: false);
SetCameraEnabled(false);
}
}
private void SetCameraEnabled(bool enabled)
{
if (portraitCamera != null)
portraitCamera.enabled = enabled;
}
private void EnsureCamera()
{
if (portraitCamera != null)
{
portraitCamera.cullingMask = 1 << portraitLayer;
portraitCamera.fieldOfView = fieldOfView;
portraitCamera.enabled = false;
return;
}
portraitCamera = PortraitCaptureUtils.CreatePortraitCamera(
transform, "PortraitCamera_ElsePlayer", portraitLayer, fieldOfView);
}
private void EnsureRenderTexture()
{
if (outputTexture != null)
{
if (portraitCamera != null) portraitCamera.targetTexture = outputTexture;
return;
}
outputTexture = PortraitCaptureUtils.CreatePortraitRT("ElsePlayerPortraitRT", portraitCamera);
}
public enum FaceAxis { Forward, Back, Up, Down, Right, Left }
}
}