public override void OnImportAsset(AssetImportContext ctx) { try { if (Utils.IsUsingADBV1()) { ctx.LogImportWarning("Search indexes are not supported with the Asset Database V1"); return; } var db = ScriptableObject.CreateInstance <SearchDatabase>(); db.Import(ctx.assetPath); ctx.AddObjectToAsset("index", db); ctx.SetMainObject(db); #if UNITY_2020_1_OR_NEWER ctx.DependsOnCustomDependency(nameof(CustomObjectIndexerAttribute)); #endif hideFlags |= HideFlags.HideInInspector; } catch (SearchDatabaseException ex) { ctx.LogImportError(ex.Message, AssetDatabase.LoadMainAssetAtPath(AssetDatabase.GUIDToAssetPath(ex.guid))); } }
public static UdsTexture FromNode(AssetImportContext ctx, XmlNode node) { UdsTexture tex = new UdsTexture(); /* * <Texture name="concrete_cast-in-place_formwork_wood_boards_bump_0" texturemode="0" texturefilter="3" textureaddressx="0" textureaddressy="0" rgbcurve="-1.000000" * srgb="0" file="rac_basic_sample_project_local_copy-3DView-UE4_Assets/concrete.cast-in-place.formwork.wood.boards.bump.jpg"> * <Hash value="c99e25a6f94199ce085a6e78e56639f2"/> * </Texture> */ tex.name = node.Attributes["name"].Value; tex.filePath = node.Attributes["file"].Value; if (Regex.Match(tex.filePath, @"\.ies$", RegexOptions.IgnoreCase).Success) { ctx.LogImportWarning(String.Format("Texture Reference \"{0}\" to IES light profile \"{1}\" cannot be resolved: IES light profile import is not implemented.", tex.name, tex.filePath)); return(null); } tex.fullyQualifiedPath = Path.Combine(Path.GetDirectoryName(ctx.assetPath), tex.filePath); var texAssetObj = AssetDatabase.LoadAssetAtPath(tex.fullyQualifiedPath, typeof(Texture)); if (texAssetObj != null) { tex.assetRef = (Texture)texAssetObj; var texImporterObj = AssetImporter.GetAtPath(tex.fullyQualifiedPath); // load import settings for possible later adjustment once we know what this will be used for if (texImporterObj != null) { tex.importer = (TextureImporter)texImporterObj; } } if (tex.assetRef == null || tex.importer == null) { ctx.LogImportError(String.Format("UdsTexture::FromNode: Asset does not exist at path \"{0}\"", tex.fullyQualifiedPath)); } return(tex); }
/// <summary> /// Generates a 2D texture from an image file /// </summary> /// <param name="imagePath">The path to the png or jpg file</param> /// <param name="name">The name of the texture object. Defaults to the ase file name + Texture</param> /// <returns></returns> public Texture2D GenerateTexture(string imagePath, string name = null) { if (TextureCreationOptions == null) { TextureCreationOptions = TextureCreationOptions.Default; } var texture = new Texture2D(2, 2, TextureFormat.ARGB32, false); if (!texture.LoadImage(File.ReadAllBytes(imagePath))) { ctx.LogImportWarning("Texture loading not successful"); } texture.wrapMode = TextureCreationOptions.textureWrapMode; texture.filterMode = TextureCreationOptions.filterMode; texture.alphaIsTransparency = TextureCreationOptions.alphaIsTransparency; texture.name = string.IsNullOrWhiteSpace(name) ? AseFileNoExt + "Texture" : name; return(texture); }
/// <summary> /// Common method performing the import of the asset /// </summary> /// <param name="ctx">Asset importer context.</param> public override void OnImportAsset(AssetImportContext ctx) { engine.TextureGenerationType = TextureImporterType.Default; Texture cookieTextureCube = null; Texture cookieTexture2D = null; string iesFilePath = Path.Combine(Path.GetDirectoryName(Application.dataPath), ctx.assetPath); string errorMessage = engine.ReadFile(iesFilePath); if (string.IsNullOrEmpty(errorMessage)) { iesMetaData.FileFormatVersion = engine.FileFormatVersion; iesMetaData.IESPhotometricType = engine.GetPhotometricType(); iesMetaData.Manufacturer = engine.GetKeywordValue("MANUFAC"); iesMetaData.LuminaireCatalogNumber = engine.GetKeywordValue("LUMCAT"); iesMetaData.LuminaireDescription = engine.GetKeywordValue("LUMINAIRE"); iesMetaData.LampCatalogNumber = engine.GetKeywordValue("LAMPCAT"); iesMetaData.LampDescription = engine.GetKeywordValue("LAMP"); (iesMetaData.IESMaximumIntensity, iesMetaData.IESMaximumIntensityUnit) = engine.GetMaximumIntensity(); string warningMessage; (warningMessage, cookieTextureCube) = engine.GenerateCubeCookie(iesMetaData.CookieCompression, (int)iesMetaData.iesSize); if (!string.IsNullOrEmpty(warningMessage)) { ctx.LogImportWarning($"Cannot properly generate IES Cube texture: {warningMessage}"); } cookieTextureCube.IncrementUpdateCount(); (warningMessage, cookieTexture2D) = engine.Generate2DCookie(iesMetaData.CookieCompression, iesMetaData.SpotAngle, (int)iesMetaData.iesSize, iesMetaData.ApplyLightAttenuation); if (!string.IsNullOrEmpty(warningMessage)) { ctx.LogImportWarning($"Cannot properly generate IES 2D texture: {warningMessage}"); } cookieTexture2D.IncrementUpdateCount(); } else { ctx.LogImportError($"Cannot read IES file '{iesFilePath}': {errorMessage}"); } string iesFileName = Path.GetFileNameWithoutExtension(ctx.assetPath); var iesObject = ScriptableObject.CreateInstance <IESObject>(); iesObject.iesMetaData = iesMetaData; GameObject lightObject = new GameObject(iesFileName); lightObject.transform.localEulerAngles = new Vector3(90f, 0f, iesMetaData.LightAimAxisRotation); Light light = lightObject.AddComponent <Light>(); light.type = (iesMetaData.PrefabLightType == IESLightType.Point) ? LightType.Point : LightType.Spot; light.intensity = 1f; // would need a better intensity value formula light.range = 10f; // would need a better range value formula light.spotAngle = iesMetaData.SpotAngle; ctx.AddObjectToAsset("IES", iesObject); ctx.SetMainObject(iesObject); IESImporter.createRenderPipelinePrefabLight?.Invoke(ctx, iesFileName, iesMetaData.UseIESMaximumIntensity, iesMetaData.IESMaximumIntensityUnit, iesMetaData.IESMaximumIntensity, light, (iesMetaData.PrefabLightType == IESLightType.Point) ? cookieTextureCube : cookieTexture2D); if (cookieTextureCube != null) { cookieTextureCube.name = iesFileName + "-Cube-IES"; ctx.AddObjectToAsset(cookieTextureCube.name, cookieTextureCube); } if (cookieTexture2D != null) { cookieTexture2D.name = iesFileName + "-2D-IES"; ctx.AddObjectToAsset(cookieTexture2D.name, cookieTexture2D); } }
public override void OnImportAsset(AssetImportContext ctx) { Tools.EnsureDirectoryExists(PREFAB_DESTINATION_DIRECTORY); int idx = ctx.assetPath.LastIndexOf("geobin"); if (-1 == idx) { ctx.LogImportWarning("File is not located under 'geobin', skipping"); return; } if (null == s_runtime_data) //!m_RuntimeReady { s_runtime_data = new RuntimeData(); string basepath = ctx.assetPath.Substring(0, idx - 1); if (!basepath.EndsWith("/") && basepath[basepath.Length - 1] != Path.DirectorySeparatorChar) { basepath += Path.DirectorySeparatorChar; } s_runtime_data.prepare(basepath); } if (m_previous_asset == null || m_previous == null) { sg = SceneGraph.loadWholeMap(ctx.assetPath); } else { Debug.Log("Reusing previously loaded graph"); sg = m_previous; } m_previous = sg; m_previous_asset = ctx.assetPath; var top_nodes = sg.calculateUsages(); if (top_nodes.Count != 0) { bool some_prefabs_were_missing = false; foreach (var pair in top_nodes) { GameObject top_level = convertFromRoot(pair.Value); if (top_level == null) { some_prefabs_were_missing = true; } else { GameObject.DestroyImmediate(top_level); } } foreach (KeyValuePair <SceneNode, GameObject> pair in m_imported_prefabs) { if (!top_nodes.ContainsValue(pair.Key)) { GameObject.DestroyImmediate(pair.Value); // remove all non-top-level prefabs from scene. } } if (some_prefabs_were_missing == false) { createTopNodesInstances(top_nodes); } else { string saved_ones = String.Join("\n", m_created_prefabs); ctx.LogImportWarning(String.Format( "The following prefab assets were missing and were created, please retry: {0}", saved_ones)); } } }
public override void OnImportAsset(AssetImportContext ctx) { var stringsMap = new Dictionary <string, string>(); try { using (var stringsFile = File.OpenText(ctx.assetPath)) { string entry; string category = null; while ((entry = this.ReadEntry(stringsFile)) != null) { entry = entry.TrimEnd(); if (entry.StartsWith("[") && entry.EndsWith("]")) { category = entry.Substring(1, entry.Length - 2); continue; } // comments can be escaped with \; var commentPos = entry.IndexOf(';'); if (commentPos > 0 && entry[commentPos - 1] != '\\') { commentPos = -1; } if (commentPos >= 0) { entry = entry.Substring(0, commentPos); } // ignore whitespace or completely commented lines if (entry.All(char.IsWhiteSpace)) { continue; } var splitPos = entry.IndexOf('='); if (splitPos == -1) { ctx.LogImportWarning($"missing delimited in line '{entry}'"); continue; } var key = entry.Substring(0, splitPos); var value = entry.Substring(splitPos + 1).Trim(); if (category != null) { key = $"{category}.{key}"; } if (stringsMap.ContainsKey(key)) { ctx.LogImportWarning($"duplicate key '{key}'"); } stringsMap.Add(key, value); } } } catch (Exception e) { stringsMap.Clear(); ctx.LogImportError(e.ToString()); } var strings = Strings.Create(stringsMap); ctx.AddObjectToAsset(Path.GetFileName(ctx.assetPath), strings); ctx.SetMainObject(strings); }
public override void OnImportAsset(AssetImportContext ctx) { var mesh = new Mesh(); ctx.AddObjectToAsset("mesh0", mesh); ctx.SetMainObject(mesh); using (BinaryReader reader = new BinaryReader(File.Open(ctx.assetPath, FileMode.Open))) { byte[] searchBuf = new byte[32]; var markerStr = "DatasmithMeshSourceModel"; byte[] marker = System.Text.Encoding.ASCII.GetBytes(markerStr); uint markerLength = (uint)markerStr.Length; bool didFindMarker = false; while (reader.BaseStream.Position < (reader.BaseStream.Length - markerLength)) { reader.BaseStream.Read(searchBuf, 0, (int)markerLength); if (0 == memcmp(searchBuf, marker, markerLength)) { reader.BaseStream.Position += 2; // Skip 2 extra null bytes after the marker didFindMarker = true; break; } // rewind to 1 byte after the previous read position reader.BaseStream.Position -= (markerLength - 1); } if (!didFindMarker) { throw new Exception("Couldn't find marker " + markerStr + " in file " + ctx.assetPath); } for (uint i = 0; i < 6; ++i) { if (reader.ReadUInt32() != 0) { Console.Out.WriteLine("Warning: expected all zeros between marker and start of material index array"); } } reader.ReadUInt32(); // length1 reader.ReadUInt32(); // length2 reader.ReadUInt32(); // unknown 9c 00 00 00 reader.ReadUInt32(); // unknown 00 00 00 00 reader.ReadUInt32(); // unknown 01 00 00 00 reader.ReadUInt32(); // unknown 00 00 00 00 uint materialIndexCount = reader.ReadUInt32(); uint[] materialIndices = new uint[materialIndexCount]; for (uint i = 0; i < materialIndexCount; ++i) { materialIndices[i] = reader.ReadUInt32(); } uint unknownCount = reader.ReadUInt32(); uint[] unknownData = new uint[unknownCount]; for (uint i = 0; i < unknownCount; ++i) { unknownData[i] = reader.ReadUInt32(); } List <Vector3> vertices = new List <Vector3>(); Dictionary <int, int> indexRemap = new Dictionary <int, int>(); { // Collapse vertices and generate an index remapping table Dictionary <Vector3, int> uniqueVertices = new Dictionary <Vector3, int>(); int fileVertexCount = (int)reader.ReadUInt32(); int vertexLimit = 524288; if (fileVertexCount > vertexLimit) { ctx.LogImportError(String.Format("UdsMeshImporter: Sanity check failed: File {0} has too many vertices ({1}, limit is {2}) -- returning empty mesh.", ctx.assetPath, fileVertexCount, vertexLimit)); return; } for (int i = 0; i < fileVertexCount; ++i) { Vector3 v = new Vector3(); // Adjust scale from cm -> meters v.x = reader.ReadSingle() * 0.01f; v.y = reader.ReadSingle() * 0.01f; v.z = reader.ReadSingle() * 0.01f; if (!uniqueVertices.ContainsKey(v)) { vertices.Add(v); uniqueVertices.Add(v, vertices.Count - 1); } indexRemap.Add(i, uniqueVertices[v]); } /* * if (vertices.Count < fileVertexCount) { * Debug.Log(String.Format("Vertex position remapping removed {0} nonunique positions", fileVertexCount - vertices.Count)); * } */ } uint indexCount = reader.ReadUInt32(); int[] triangleIndices = new int[indexCount]; for (uint triIdx = 0; triIdx < (indexCount / 3); ++triIdx) { triangleIndices[(triIdx * 3) + 0] = indexRemap[(int)reader.ReadUInt32()]; triangleIndices[(triIdx * 3) + 1] = indexRemap[(int)reader.ReadUInt32()]; triangleIndices[(triIdx * 3) + 2] = indexRemap[(int)reader.ReadUInt32()]; } reader.ReadUInt32(); // unknown-zero, maybe a count of an unused field reader.ReadUInt32(); // unknown-zero, maybe a count of an unused field uint normalCount = reader.ReadUInt32(); Vector3[] normals = new Vector3[normalCount]; for (uint i = 0; i < normalCount; ++i) { normals[i].x = reader.ReadSingle(); normals[i].y = reader.ReadSingle(); normals[i].z = reader.ReadSingle(); } uint uvCount = reader.ReadUInt32(); Vector2[] uvs = new Vector2[uvCount]; for (uint i = 0; i < uvCount; ++i) { uvs[i].x = reader.ReadSingle(); uvs[i].y = reader.ReadSingle(); } // Datasmith hands us per-face-vertex normals and UVs, which Unity can't handle. // Use the Datasmith-supplied index array to write new per-submesh (material group) position/normal/UV buffers. { var materialToSubmesh = new SortedDictionary <uint, uint>(); uint submeshCount = 0; for (uint triIdx = 0; triIdx < materialIndexCount; ++triIdx) { uint midx = materialIndices[triIdx]; if (!materialToSubmesh.ContainsKey(midx)) { materialToSubmesh[midx] = submeshCount; submeshCount += 1; } } List <Vector3> cookedPositions = new List <Vector3>(); List <Vector2> cookedUVs = new List <Vector2>(); List <Vector3> cookedNormals = new List <Vector3>(); List <List <int> > cookedSubmeshIndices = new List <List <int> >(); List <Dictionary <Hash128, int> > vertexCollapseData = new List <Dictionary <Hash128, int> >(vertices.Count); // Prepopulate the vertex-collapse list with empty dicts for (int vIdx = 0; vIdx < vertices.Count; ++vIdx) { vertexCollapseData.Add(new Dictionary <Hash128, int>()); } for (uint submeshIndex = 0; submeshIndex < submeshCount; ++submeshIndex) { List <int> thisSubmeshIndices = new List <int>(); for (uint triIdx = 0; triIdx < materialIndexCount; ++triIdx) { if (materialToSubmesh[materialIndices[triIdx]] != submeshIndex) { continue; // this triangle is not relevant in this submesh. } for (uint triVIdx = 0; triVIdx < 3; ++triVIdx) { uint triVIdx_adj = 2 - triVIdx; // Adjusted to swap winding order int positionIndex = triangleIndices[(triIdx * 3) + triVIdx_adj]; Vector3 fvP = vertices[positionIndex]; Vector2 fvUV = uvs[(triIdx * 3) + triVIdx_adj]; Vector3 fvN = normals[(triIdx * 3) + triVIdx_adj]; // Try and find an existing vertex/normal/UV set to reuse // We already collapsed coincident positions while reading the vertex and index buffers, so we can partition our search by position index. Dictionary <Hash128, int> collapseData = vertexCollapseData[positionIndex]; Hash128 targetHash = NUVHash(fvN, fvUV); int targetVIdx; if (collapseData.ContainsKey(targetHash)) { // Match found, reuse the previous vertex targetVIdx = collapseData[targetHash]; } else { // No match found, so we add it cookedPositions.Add(fvP); cookedUVs.Add(fvUV); cookedNormals.Add(fvN); targetVIdx = cookedPositions.Count - 1; collapseData.Add(targetHash, targetVIdx); } thisSubmeshIndices.Add(targetVIdx); } } cookedSubmeshIndices.Add(thisSubmeshIndices); } mesh.Clear(); if (cookedPositions.Count > 65535) { ctx.LogImportWarning(String.Format("Mesh \"{0}\" has more than 65535 vertices ({1}) and requires a 32-bit index buffer. This mesh may not render correctly on all platforms.", ctx.assetPath, cookedPositions.Count)); mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; } mesh.SetVertices(cookedPositions); mesh.SetUVs(0, cookedUVs); mesh.SetNormals(cookedNormals); mesh.subMeshCount = (int)submeshCount; for (uint submeshIndex = 0; submeshIndex < submeshCount; ++submeshIndex) { mesh.SetIndices(cookedSubmeshIndices[(int)submeshIndex].ToArray(), MeshTopology.Triangles, (int)submeshIndex); } // Generate lightmap UVs if (materialIndexCount > 50000 /*triangles*/) { if (m_ForceLightmapUVGeneration) { UnityEditor.Unwrapping.GenerateSecondaryUVSet(mesh); } else { ctx.LogImportWarning(String.Format("Mesh \"{0}\": lightmap UVs won't automatically be generated due to complexity limits. Turn on \"Force Lightmap UV generation\" to override.", ctx.assetPath)); } } mesh.RecalculateBounds(); mesh.RecalculateTangents(); } } }
public void LogWarning(string message, UnityEngine.Object context = null) { ctx.LogImportWarning(message, context); }
public override void OnImportAsset(AssetImportContext ctx) { byte[] bytes = File.ReadAllBytes(ctx.assetPath); Texture2D sourceTexture = new Texture2D(1, 1, TextureFormat.ARGB32, false); sourceTexture.LoadImage(bytes); originalWidth = sourceTexture.width; originalHeight = sourceTexture.height; cols = sourceTexture.width / pageSize; if (cols == 0) { cols = 1; } rows = sourceTexture.height / pageSize; if (rows == 0) { rows = 1; } bool changedPageSize = false; while (cols * rows > 2048) { pageSize *= 2; cols /= 2; rows /= 2; changedPageSize = true; } if (changedPageSize) { ctx.LogImportWarning("Texture Width * Texture Height * Page Size must be less or equal to 2048, so the Page Size is increased to " + pageSize); } int resizeX = sourceTexture.width; int resizeY = sourceTexture.height; if (sourceTexture.width % pageSize != 0) { resizeX = cols * pageSize; } if (sourceTexture.height % pageSize != 0) { resizeY = rows * pageSize; } if (resizeX != sourceTexture.width || resizeY != sourceTexture.height) { TextureScaler.Scale(sourceTexture, resizeX, resizeY); ctx.LogImportWarning("The width and height of the texture must be equal to Page Size * N, so the texture size is changed to " + resizeX + "x" + resizeY); } Texture2DArray array = InitTexture2DArray(sourceTexture, ctx); ctx.AddObjectToAsset("_MainTex", array); ctx.SetMainObject(array); DestroyImmediate(sourceTexture); }
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 }