using Oculus.Interaction; using Oculus.Interaction.Surfaces; using System.Collections.Generic; using UnityEngine; /// /// Base for BaseModelBehaviour and ChildModelBehaviour /// 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 _children = new List(); public readonly Dictionary portDict = new Dictionary(); //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 bool afterBirth = 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().Color = value; } } } } void Start() { Init(); } void Init() { if (ModelManager == null) ModelManager = FindFirstObjectByType(); } 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.localRotation *= Quaternion.Inverse(Model.Rotation); transform.localPosition -= Model.Offset; transform.localScale -= Model.Scale; } //kill old children foreach (var compo in GetComponentsInChildren()) { 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); } afterBirth = true; } 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(); cmb.ChildModel = ModelManager.GetById(id); cmb.Parent = this; Model.Ports[portNum].Apply(_children[portNum].transform); portDict[portNum] = cmb.ChildModel; Exploder.HandleModelChange(); } /// /// Spawns all Port-GO's, should be called only on Model Switch after cleanup /// /// Model the New Model to spawn ports from 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(); _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; if (newModel.Passthrough) { cmb.meshRenderer.material = newModel.Material; cmb.meshRenderer.sharedMaterial = newModel.Material; } port.Apply(child.transform); // move to correct position portDict[i] = cmb.ChildModel; } } /// /// -> see docs/exampleExport.json /// this doesn't include leading+trailing { } or commas, just the content /// this is recursive /// /// Whether all Ports or just the Chooseable ones should be exported /// an unfinished Json of this Model and all its children 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().ExportJsonBase() + "}"; if (i != Model.Ports.Count - 1) //if not the last { export += ","; } } export += "]"; return export; } /// /// Exports the current Model as a correct JSON /// -> see docs/exampleExport.json /// /// An 'unformatted' JSON 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().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(); afterBirth = false; } }