Files
vr-configurator/vr-configurator/Assets/Scripts/Models/ModelBehaviour.cs
2025-07-17 22:15:53 +02:00

327 lines
10 KiB
C#

using Oculus.Interaction;
using Oculus.Interaction.Surfaces;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Base for BaseModelBehaviour and ChildModelBehaviour
/// </summary>
public class ModelBehaviour : MonoBehaviour, IResettable
{
public ModelManager ModelManager;
public Exploder Exploder;
Model _model;
internal Model Model
{
get
{
return _model;
}
set
{
UpdateModel(value);
_model = value;
LateUpdateModel();
}
}
readonly List<GameObject> _children = new List<GameObject>();
public readonly Dictionary<int, ChildModel> portDict = new Dictionary<int, ChildModel>(); //for CMS marker
public MeshFilter meshFilter;
public MeshRenderer meshRenderer;
public MeshCollider meshCollider;
public ColliderSurface colliderSurface;
public InteractableUnityEventWrapper interactable;
public RayInteractable rayInteractable;
bool lateInited = false;
public PortSelector PortSelector;
public int Index = -1;
Color _color = Color.black;
Color Color
{
get
{
return _color;
}
set
{
var clonedMaterial = new Material(meshRenderer.material); //clone da sonst alle anderen mit dem mat auch colorchanged werden
clonedMaterial.color = value;
meshRenderer.material = clonedMaterial;
Debug.Log($"{name}: Set color from {_color} to {value}");
_color = value;
if (Model.Passthrough)
{
Debug.Log($"{name}: Passthrough {_children.Count}");
foreach (GameObject go in _children)
{
Debug.Log($"{name}: coloring {go.name}");
go.GetComponent<ChildModelBehaviour>().Color = value;
}
}
}
}
void Start()
{
Init();
}
void Init()
{
if (ModelManager == null)
ModelManager = FindFirstObjectByType<ModelManager>();
}
void LateUpdate()
{
if (!lateInited)
{
if (interactable.WhenSelect == null)
{
return;
}
interactable.WhenSelect.AddListener(OnSelect); //TODO: the root of all "frequently called", which is wrong
lateInited = true;
}
}
void UpdateModel(Model newModel)
{
Debug.Log($"UpdateModel: {name} updates from {(Model == null ? "null" : Model.NameId)} to {newModel.NameId}");
Init(); // I love race conditions
if (newModel is BaseModel)
{
name = "BaseModel:" + newModel.NameId;
}
else if (newModel is ChildModel cModel)
{
Debug.Log($"UpdateModel Name: {name} to {cModel.ParentPort}:{newModel.NameId}");
name = cModel.ParentPort + ":" + newModel.NameId;
}
//unapply previous model offset
if (Model != null)
{
Debug.Log($"UpdateModel: Model {name} reset old {Model} offsets.");
transform.localPosition -= Model.Offset;
transform.localRotation *= Quaternion.Inverse(Model.Rotation);
transform.localScale -= Model.Scale;
}
//kill old children
foreach (var compo in GetComponentsInChildren<ChildModelBehaviour>())
{
if (this is ChildModelBehaviour cmb && compo == cmb) //dont delete ourselves
{
continue;
}
Debug.Log($"UpdateModel: Destroying {name}'s Child {compo.gameObject.name}");
Destroy(compo.gameObject); // will be destroyed next frame
_children.Remove(compo.gameObject);
}
//change ourselves
meshFilter.mesh = newModel.Mesh;
meshFilter.sharedMesh = newModel.Mesh;
meshRenderer.material = newModel.Material;
meshRenderer.sharedMaterial = newModel.Material;
meshCollider.sharedMesh = newModel.Mesh;
transform.localRotation = newModel.Rotation;
transform.localScale = newModel.Scale;
transform.localPosition += newModel.Offset;
//spawn new childPorts
if (newModel.HasPorts())
{
Debug.Log($"Spawning {gameObject.name}'s ports");
SpawnChildPorts(newModel);
}
}
void LateUpdateModel()
{
Debug.Log($"LateUpdateModel");
Exploder.HandleModelChange();
}
public void UpdateChild(int portNum, string id) //onclick change model
{
if (portNum >= _children.Count)
{
Debug.LogWarning($"Index {portNum} out of bounds {_children.Count}");
}
Model.Ports[portNum].Unapply(_children[portNum].transform);
//set new
var cmb = _children[portNum].GetComponent<ChildModelBehaviour>();
cmb.ChildModel = ModelManager.GetById(id);
cmb.Parent = this;
Model.Ports[portNum].Apply(_children[portNum].transform);
portDict[portNum] = cmb.ChildModel;
Exploder.HandleModelChange();
}
/// <summary>
/// Spawns all Port-GO's, should be called only on Model Switch <b>after</b> cleanup
/// </summary>
/// <param name="newModel"><c>Model</c> the New Model to spawn ports from</param>
void SpawnChildPorts(Model newModel)
{
Debug.Log($"SpawnChildPorts: {name} : {newModel.NameId} spawns {newModel.Ports.Count} Ports");
if (ModelManager == null)
{
Debug.LogError("ModelManager not found");
return;
}
for (int i = 0; i < newModel.Ports.Count; i++)
{
var port = newModel.Ports[i];
Debug.Log(i + ". Creating port " + port.PortID);
// name is set in UpdateModel of own model
GameObject child = Spawn.GO(ModelManager.childModelPrefab, transform, Vector3.zero, "unattended child");
child.SetActive(true);
ChildModelBehaviour cmb = child.GetComponent<ChildModelBehaviour>();
_children.Add(child);
cmb.Index = i;
var childModel = ModelManager.GetById(port.DefaultId);
if (childModel.Mesh == null)
{
Debug.LogError("Default Mesh Not Found, destroying child");
Destroy(child);
continue;
}
//Debug.Log($"POS {child.transform.gameObject.name} pos {child.transform.localPosition} {child.transform.rotation} {child.transform.localScale}");
cmb.Parent = this;
cmb.ChildModel = childModel;
port.Apply(child.transform); // move to correct position
portDict[i] = cmb.ChildModel;
}
}
/// <summary>
/// -> see docs/exampleExport.json
/// this doesn't include leading+trailing { } or commas, just the content
/// this is recursive
/// </summary>
/// <param name="chooseableOnly">Whether all Ports or just the Chooseable ones should be exported</param>
/// <returns>an unfinished Json of this Model and all its children</returns>
string ExportJsonBase(bool chooseableOnly = true)
{
string export = "";
if (Model is BaseModel)
{
export += "\"baseModel\": true,";
}
export += $"\"modelId\": \"{Model.NameId}\"";
if(Model is ChildModel cm)
{
export += $", \"portId\": \"{cm.ParentPort}\"";
}
if (Model.HasColor())
{
export += $", \"color\": \"{Color}\"";
}
if (!Model.HasPorts())
{
return export;
}
export += ", \"ports\": [";
for (int i = 0; i < Model.Ports.Count; i++)
{
if (chooseableOnly && !Model.Ports[i].Chooseable)
{
continue;
}
export += "{ \"i\": " + i +", " + _children[i].GetComponent<ChildModelBehaviour>().ExportJsonBase() + "}";
if(i != Model.Ports.Count-1) //if not the last
{
export += ",";
}
}
export += "]";
return export;
}
/// <summary>
/// Exports the current Model as a correct JSON
/// -> see docs/exampleExport.json
/// </summary>
/// <returns>An 'unformatted' JSON</returns>
public string ExportJson()
{
return "{" + ExportJsonBase() + "}";
}
public void UpdateChildColor(int portIndex, Color color)
{
if (portIndex < 0 || portIndex >= _children.Count)
{
Debug.LogWarning($"PortIndex {portIndex} invalid: there are {_children.Count} children.");
return;
}
_children[portIndex].GetComponent<ChildModelBehaviour>().Color = color;
}
public void ClearChildren()
{
Debug.Log($"Removing Children of {name}...");
foreach (var child in _children)
{
if (child != null)
{
Destroy(child);
}
}
_children.Clear();
Debug.Log("Removed.");
}
//model clicked
void OnSelect()
{
Debug.Log($"OnClick model {name} {Index}");
if (Index == -1)
{
Debug.LogWarning($"Clicked on Initialized/BaseModel, doing nothing");
return;
}
if (this is ChildModelBehaviour cmb)
{
if (cmb.Parent is BaseModelBehaviour)
{
PortSelector.mapPSB[Index].OnClick();
}
else //subchild
{
var parent = cmb;
while (parent)
{
if (parent.Parent is BaseModelBehaviour)
{
Debug.Log($"Parent {parent.name} is root, clicking on him");
PortSelector.mapPSB[parent.Index].OnClick();
return;
}
if (parent.Parent is ChildModelBehaviour cmbParent)
{
parent = cmbParent;
continue;
}
Debug.LogWarning($"Skipping {parent.name}, weird");
break;
}
}
}
}
public void ResetThis()
{
Debug.Log($"Resetting {name}...");
ClearChildren();
}
}