private void Start() { rb = transform.GetComponent <Rigidbody>(); pickup = (VRC.SDK3.Components.VRCPickup)transform.GetComponent(typeof(VRC.SDK3.Components.VRCPickup)); hook.parent = transform.parent; hook.name = "Hook_" + hook.GetSiblingIndex(); }
private void ImportActorChildren(AssetImportContext ctx, GameObject parentObject, XmlNode containerNode) { foreach (XmlNode node in containerNode.ChildNodes) { if (!(node.Name == "Actor" || node.Name == "ActorMesh" || node.Name == "Light")) { continue; // Only examine supported nodes } String objName = node.Attributes["label"].Value + "_" + node.Attributes["name"].Value; GameObject obj = new GameObject(objName); obj.transform.SetParent(parentObject.transform, /*worldPositionStays=*/ false); //Do NOT call ctx.AddObjectToAsset on these GameObjects. It should only be called on the root GameObject of the hierarchy (which the Unity manual fails to mention, of course.) // Calling ctx.AddObjectToAsset on the child GameObjects will cause Unity to crash when changing importer settings. { XmlNode xfNode = node.SelectSingleNode("child::Transform"); if (xfNode != null) { // Transform position and rotation are stored in an absolute coordinate space. TODO: not sure if Scale will be applied correctly // Datasmith units are cm, while Unity units are m; we adjust the scale of mesh vertices and of incoming Transform nodes to match. obj.transform.position = new Vector3( Single.Parse(xfNode.Attributes["tx"].Value) * 0.01f, Single.Parse(xfNode.Attributes["ty"].Value) * 0.01f, Single.Parse(xfNode.Attributes["tz"].Value) * 0.01f); obj.transform.localScale = new Vector3( Single.Parse(xfNode.Attributes["sx"].Value), Single.Parse(xfNode.Attributes["sy"].Value), Single.Parse(xfNode.Attributes["sz"].Value)); obj.transform.rotation = new Quaternion( Single.Parse(xfNode.Attributes["qx"].Value), Single.Parse(xfNode.Attributes["qy"].Value), Single.Parse(xfNode.Attributes["qz"].Value), Single.Parse(xfNode.Attributes["qw"].Value)); } } // transform processing if (node.Name == "ActorMesh") { XmlNode meshNode = node.SelectSingleNode("child::mesh"); if (meshNode != null) { String meshName = meshNode.Attributes["name"].Value; UdsStaticMesh mesh; staticMeshElements.TryGetValue(meshName, out mesh); Debug.Assert(mesh != null, String.Format("Missing StaticMesh node for \"{0}\" referenced from ActorMesh node \"{1}\"", meshName, node.Attributes["name"].Value)); if (mesh.assetRef) { MeshFilter mf = obj.AddComponent <MeshFilter>(); mf.sharedMesh = mesh.assetRef; Material[] mats = new Material[mesh.assetRef.subMeshCount]; for (int materialIdx = 0; materialIdx < mesh.assetRef.subMeshCount; ++materialIdx) { mats[materialIdx] = mesh.materialRefs[materialIdx].assetRef; } MeshRenderer mr = obj.AddComponent <MeshRenderer>(); mr.sharedMaterials = mats; String RevitLayer = node.Attributes["layer"].Value; //Dictionary<String, String> metadata = new Dictionary<string, string>(); //actorMetadata.TryGetValue(node.Attributes["name"].Value, out metadata); // Process imported metadata and try to do something reasonable with it { //String RevitCategory; //metadata.TryGetValue("Element_Category", out RevitCategory); if (RevitLayer != null) { if (Regex.Match(RevitLayer, @"Ceilings", RegexOptions.IgnoreCase).Success) { // Apply ceiling height offset Vector3 p = obj.transform.position; p.z += m_CeilingHeightOffset; obj.transform.position = p; } if (Regex.Match(RevitLayer, @"Floors", RegexOptions.IgnoreCase).Success) { // Apply floor height offset Vector3 p = obj.transform.position; p.z += m_FloorHeightOffset; obj.transform.position = p; } if (Regex.Match(RevitLayer, m_IgnoredLayerRegex, RegexOptions.IgnoreCase).Success) { // Default-hidden objects. For example, "Entourage" and "Planting" objects are not exported correctly by Datasmith (no materials/textures), so we hide them. obj.SetActive(false); } else if (Regex.Match(RevitLayer, m_StaticTangibleLayerRegex, RegexOptions.IgnoreCase).Success) { // Completely static objects that should be lightmapped and have collision enabled GameObjectUtility.SetStaticEditorFlags(obj, StaticEditorFlags.LightmapStatic | StaticEditorFlags.OccludeeStatic | StaticEditorFlags.OccluderStatic | StaticEditorFlags.BatchingStatic | StaticEditorFlags.ReflectionProbeStatic); // Collision MeshCollider collider = obj.AddComponent <MeshCollider>(); collider.cookingOptions = (MeshColliderCookingOptions.CookForFasterSimulation | MeshColliderCookingOptions.EnableMeshCleaning | MeshColliderCookingOptions.WeldColocatedVertices); } else if (Regex.Match(RevitLayer, m_StaticIntangibleLayerRegex, RegexOptions.IgnoreCase).Success) { // Completely static objects that should be lightmapped, but don't need collision GameObjectUtility.SetStaticEditorFlags(obj, StaticEditorFlags.LightmapStatic | StaticEditorFlags.OccludeeStatic | StaticEditorFlags.OccluderStatic | StaticEditorFlags.BatchingStatic | StaticEditorFlags.ReflectionProbeStatic); } else if (Regex.Match(RevitLayer, m_PhysicsPropsLayerRegex).Success) { // Clutter that can be physics-enabled MeshCollider collider = obj.AddComponent <MeshCollider>(); collider.cookingOptions = (MeshColliderCookingOptions.CookForFasterSimulation | MeshColliderCookingOptions.EnableMeshCleaning | MeshColliderCookingOptions.WeldColocatedVertices); #if USE_VRC_SDK3 if (m_SetupPhysicsProps) { collider.convex = true; Rigidbody rb = obj.AddComponent <Rigidbody>(); // rb.collisionDetectionMode = CollisionDetectionMode.Continuous; // Higher quality collision detection, but slower. // Add VRCPickup component to make the object interactable VRC.SDK3.Components.VRCPickup pickup = obj.AddComponent <VRC.SDK3.Components.VRCPickup>(); pickup.pickupable = true; pickup.allowManipulationWhenEquipped = true; // Add UdonBehaviour component to replicate the object's position VRC.Udon.UdonBehaviour udon = obj.AddComponent <VRC.Udon.UdonBehaviour>(); udon.SynchronizePosition = true; // TODO see if it's possible to only enable gravity on objects the first time they're picked up (so wall/ceiling fixtures can remain in place until grabbed) } #endif } else { ctx.LogImportWarning(String.Format("Unhandled Layer \"{0}\" -- ActorMesh \"{1}\" will not have physics and lighting behaviours automatically mapped", RevitLayer, objName)); } if (Regex.Match(RevitLayer, @"Lighting").Success) { // Turn off shadow casting on light fixtures. Light sources are usually placed inside the fixture body and we don't want the fixture geometry to block them. mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; } } } } else { ctx.LogImportError(String.Format("ActorMesh {0} mesh {1} assetRef is NULL", obj.name, meshName)); } } } // ActorMesh if (node.Name == "Light" && m_ImportLights) { Light light = obj.AddComponent <Light>(); if (node.Attributes["type"].Value == "PointLight") { light.type = LightType.Point; } else { ctx.LogImportWarning(String.Format("Light {0}: Unhandled \"type\" \"{1}\", defaulting to Point", objName, node.Attributes["type"].Value)); } { // Color temperature or RGB color XmlNode colorNode = node.SelectSingleNode("child::Color"); if (colorNode != null) { if (Int32.Parse(colorNode.Attributes["usetemp"].Value) != 0) { float colorTemperature = Single.Parse(colorNode.Attributes["temperature"].Value); // There doesn't appear to be a way to turn on color temperature mode on the Light programmatically (why?) // Convert it to RGB; algorithm borrowed from https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html float tmpKelvin = Mathf.Clamp(colorTemperature, 1000.0f, 40000.0f) / 100.0f; // Note: The R-squared values for each approximation follow each calculation float r = tmpKelvin <= 66.0f ? 255.0f : Mathf.Clamp(329.698727446f * (Mathf.Pow(tmpKelvin - 60.0f, -0.1332047592f)), 0.0f, 255.0f); // .988 float g = tmpKelvin <= 66 ? Mathf.Clamp(99.4708025861f * Mathf.Log(tmpKelvin) - 161.1195681661f, 0.0f, 255.0f) : // .996 Mathf.Clamp(288.1221695283f * (Mathf.Pow(tmpKelvin - 60.0f, -0.0755148492f)), 0.0f, 255.0f); // .987 float b = tmpKelvin >= 66 ? 255 : tmpKelvin <= 19 ? 0 : Mathf.Clamp(138.5177312231f * Mathf.Log(tmpKelvin - 10.0f) - 305.0447927307f, 0.0f, 255.0f); // .998 light.color = new Color(r / 255.0f, g / 255.0f, b / 255.0f); } else { float r = Single.Parse(colorNode.Attributes["R"].Value); float g = Single.Parse(colorNode.Attributes["G"].Value); float b = Single.Parse(colorNode.Attributes["B"].Value); light.color = new Color(r, g, b); } } } // Common light parameters if (m_SetupLightmapBaking) { light.lightmapBakeType = LightmapBakeType.Baked; } GameObjectUtility.SetStaticEditorFlags(obj, StaticEditorFlags.LightmapStatic | StaticEditorFlags.OccludeeStatic | StaticEditorFlags.OccluderStatic | StaticEditorFlags.BatchingStatic | StaticEditorFlags.ReflectionProbeStatic); } { // children node processing XmlNode childrenNode = node.SelectSingleNode("child::children"); if (childrenNode != null) { // TODO obey visible="true" / visible="false" attribute on children node ImportActorChildren(ctx, obj, childrenNode); } } // children node processing } // child loop }