public static bool UnpackCommonPart(string name, bool lightmapped, int lod) { string lodDir = lod == 0 ? lod0Dir : lod == 1 ? lod1Dir : lod2Dir; string extension = lod <= 1 ? ".fbx" : ".prefab"; if (File.Exists(Path.Combine(geometryPath, commonPartsDir, lightmapped ? lightmappedDir : "", lodDir, name + extension))) { return(true); } if (lod <= 1) { OpenDB(); var entry = commonPartsZipArchive.GetEntry(name + ".fbx"); if (entry != null) { var fileName = entry.Name; var filePath = Path.Combine(geometryPath, commonPartsDir, lightmapped ? lightmappedDir : "", lodDir, fileName); var directoryName = Path.GetDirectoryName(filePath); if (directoryName.Length > 0) { Directory.CreateDirectory(directoryName); } var zipStream = entry.Open(); var fileStream = File.Create(filePath); zipStream.CopyTo(fileStream); fileStream.Dispose(); zipStream.Dispose(); #if UNITY_EDITOR AssetDatabase.ImportAsset(filePath); #endif return(true); } } else { // Generate LOD 2. var knob = name.StartsWith("knob"); var tube = name.StartsWith("tube"); var hollow = knob ? name.EndsWith("C") : tube; var tubeOrPinHeight = name.Contains("_01_") ? 0.21f : name.Contains("_02_") ? 0.85f : 2.73f; var height = knob ? 0.178f : tubeOrPinHeight; var radius = knob ? 0.25f : tube ? 0.3377f : 0.1575f; PartUtility.CreateCommonPartLod2(name, height, radius, knob, hollow, radius - 0.08f, lightmapped); return(true); } return(false); }
private static void InstantiateCommonParts <T>(Part part, List <T> partsList, Transform parent, bool lightmapped, int lod) where T : CommonPart { int count = parent.childCount; // Instantiate common parts using locators. for (int i = 0; i < count; i++) { var commonPartLocation = parent.GetChild(i); var name = Regex.Split(commonPartLocation.name, "(_[0-9]+ 1)"); GameObject commonPartToInstantiate = null; var commonPartAvailable = PartUtility.UnpackCommonPart(name[0], lightmapped); if (commonPartAvailable) { commonPartToInstantiate = PartUtility.LoadCommonPart(name[0], lightmapped, lod); } if (commonPartToInstantiate == null) { Debug.LogError("Missing Common Part -> " + name[0]); continue; } var commonPartGO = Object.Instantiate(commonPartToInstantiate); commonPartGO.name = commonPartToInstantiate.name; var commonPartComponent = commonPartGO.AddComponent <T>(); commonPartComponent.part = part; // Set position and rotation. commonPartGO.transform.position = commonPartLocation.position; commonPartGO.transform.rotation = commonPartLocation.rotation; commonPartGO.transform.SetParent(parent, true); partsList.Add(commonPartComponent); } // Remove locators. for (int i = 0; i < count; i++) { Object.DestroyImmediate(parent.GetChild(0).gameObject); } }
void ProcessCommon(GameObject go, bool genLightmapUV, int lod) { MeshFilter[] mfs = go.GetComponentsInChildren <MeshFilter>(); foreach (var mf in mfs) { if (mf && mf.sharedMesh) { MeshTool mt = new MeshTool(mf.sharedMesh); var normalMappedLogo = go.name.StartsWith("knob") && !go.name.EndsWith("C"); // Generate lowest possible LOD. var bounds = mf.sharedMesh.bounds; var knob = go.name.StartsWith("knob"); var hollow = knob ? go.name.EndsWith("C") : go.name.StartsWith("tube"); PartUtility.CreateCommonPartLod2(go.name, bounds.size.y, bounds.extents.x, knob, hollow, bounds.extents.x - 0.08f, genLightmapUV); if (lod == 0) { mt.GenerateChamfer(1.0f / mf.transform.localScale.x); } if (normalMappedLogo) { mt.GenerateKnobNormalMapUVs(); } else { mt.ClearNormalMapUVs(); } mt.ApplyTo(mf.sharedMesh, normalMappedLogo, genLightmapUV); EditorUtility.SetDirty(mf.sharedMesh); System.GC.Collect(); } } }
public static void ProcessModelGroup(ModelGroup group, ref Vector2Int vertCount, ref Vector2Int triCount, ref Vector2Int meshCount, ref Vector2Int boxColliderCount) { bool collapseMesh = true; bool collapseCols = group.importSettings.colliders; // Keep track of how many of the removable meshes are left for each part. var partMeshCount = new Dictionary <Part, int>(); // Keep track of how many of the removable surface meshes (shell, colourChangeSurface) are left for each non-legacy part. var partSurfaceMeshCount = new Dictionary <Part, int>(); // TODO: Move orphaned colliders to group root var progress = 0; if (collapseMesh) { // Debug.Log("Collapsing Mesh for "+group.name); // Optimization settings bool doSort = group.optimizations.HasFlag(ModelGroup.Optimizations.SortFrontToBack); bool canRemoveTubesAndPins = group.optimizations.HasFlag(ModelGroup.Optimizations.RemoveTubesAndPins); bool canRemoveKnobs = group.optimizations.HasFlag(ModelGroup.Optimizations.RemoveKnobs); bool canRemoveCompletelyInvisible = group.optimizations.HasFlag(ModelGroup.Optimizations.RemoveInvisible); bool cullBackfaces = group.optimizations.HasFlag(ModelGroup.Optimizations.BackfaceCulling); bool randomizeNormals = group.randomizeNormals; // Split into different meshes depending on whether they have knobs or are transparent. // 0 = No knobs = Fast track to avoid normal mapping when it isn't required // 1 = Knobs = Slow track needs normal mapping // 2 = Transparent No Knobs = Fast track to avoid normal mapping when it isn't required. // 3 = Transparent Knobs = Slow track since we will not render transparent bricks during visibility tests. MeshHelper[] meshHelpers = new MeshHelper[] { new MeshHelper(), new MeshHelper(), new MeshHelper(), new MeshHelper() }; Matrix4x4 groupMatrix = group.transform.localToWorldMatrix; Matrix4x4 groupMatrixInv = groupMatrix.inverse; // Collect all parts. var parts = group.GetComponentsInChildren <Part>(); // Collect relevant part mesh renderers along with mesh type information and a randomized rotation for each part. List <PartMeshRenderer> mrs = new List <PartMeshRenderer>(); foreach (var part in parts) { if (progress++ % 200 == 0) { EditorUtility.DisplayProgressBar("Processing", "Collecting part renderers.", ((float)progress / parts.Length) * 0.05f); } partMeshCount[part] = 0; if (part.legacy) { // Legacy parts only have unstructured meshes. foreach (var renderer in part.GetComponentsInChildren <MeshRenderer>(true)) { var partMeshRenderer = new PartMeshRenderer() { part = part, rendererer = renderer, type = MeshType.Legacy }; mrs.Add(partMeshRenderer); partMeshCount[part]++; } } else { partSurfaceMeshCount[part] = 0; foreach (var renderer in part.GetComponentsInChildren <MeshRenderer>(true)) { var parentName = renderer.transform.parent.name; // First check if it is a decoration surface since we will not include them in the processing. if (parentName == "DecorationSurfaces") { continue; } var partMeshRenderer = new PartMeshRenderer() { part = part, rendererer = renderer.GetComponent <MeshRenderer>() }; if (parentName == "Knobs") // Is it a knob? { partMeshRenderer.type = MeshType.Knob; } else if (parentName == "Tubes") // Is it a tube? { partMeshRenderer.type = MeshType.Tube; } else if (parentName == "ColourChangeSurfaces") // Is it a colour change surface? { partMeshRenderer.type = MeshType.ColourChange; partSurfaceMeshCount[part]++; } else // It must be the shell. { partMeshRenderer.type = MeshType.Shell; partSurfaceMeshCount[part]++; } mrs.Add(partMeshRenderer); partMeshCount[part]++; } } } Vector3 camPos = Vector3.zero; Vector3 camDir = Vector3.zero; if (group.views.Count == 0 && Camera.main) { camPos = Camera.main.transform.position; camDir = Camera.main.transform.forward; } else if (group.views.Count > 0) { camPos = group.views[0].position; camDir = group.views[0].rotation * Vector3.forward; } else { doSort = false; Debug.LogError("No views specified for front-to-back geometry sorting. Disabling!"); } if (doSort) { Vector3 sortDir; if (group.views.Count == 0) { sortDir = camDir; } else { sortDir = group.views[0].rotation * Vector3.forward; } mrs.Sort(delegate(PartMeshRenderer a, PartMeshRenderer b) { float dA = Vector3.Dot(a.rendererer.bounds.center, sortDir); float dB = Vector3.Dot(b.rendererer.bounds.center, sortDir); return(dA.CompareTo(dB)); }); } // Multiple passes // - Collect all meshes // - Remove original meshfilters and renderers // - Determine which Optimization level can be used (for legacy meshes only) or if mesh can be discarded completely // - Remove backfaces // - Build combined mesh // Collect meshes List <MeshInstance> instances = new List <MeshInstance>(); for (int i = 0; i < mrs.Count; ++i) { if (i % 200 == 0) { EditorUtility.DisplayProgressBar("Processing", "Collecting part meshes.", 0.05f + (float)i / mrs.Count * 0.05f); } MeshFilter mf = mrs[i].rendererer.GetComponent <MeshFilter>(); if (mf) { if (mrs[i].rendererer.enabled && mrs[i].rendererer.gameObject.activeInHierarchy) { Mesh source = mf.sharedMesh; if (source) { meshCount.x++; triCount.x += (int)source.GetIndexCount(0) / 3; vertCount.x += source.vertexCount; Material material = mrs[i].rendererer.sharedMaterial; MeshInstance c = new MeshInstance(); c.mesh = source; c.matrix = groupMatrixInv * mf.transform.localToWorldMatrix; c.worldMatrix = mf.transform.localToWorldMatrix; c.material = material; c.transparent = material && material.color.a < 1.0; c.part = mrs[i].part; c.type = mrs[i].type; c.up = mf.transform.up; c.bounds = mrs[i].rendererer.bounds; instances.Add(c); } } Object.DestroyImmediate(mf); } Object.DestroyImmediate(mrs[i].rendererer); } List <Vector3> viewPositions = new List <Vector3>(); List <Vector3> viewDirections = new List <Vector3>(); List <Matrix4x4> viewMatrices = new List <Matrix4x4>(); List <Matrix4x4> projectionMatrices = new List <Matrix4x4>(); List <Plane[]> viewFrustums = new List <Plane[]>(); if (group.views.Count == 0 && Camera.main) { viewMatrices.Add(Camera.main.worldToCameraMatrix); if (Camera.main.orthographic) { viewDirections.Add(camDir); } else { viewPositions.Add(camPos); } viewFrustums.Add(GeometryUtility.CalculateFrustumPlanes(Camera.main)); projectionMatrices.Add(Camera.main.projectionMatrix); } else if (group.views.Count > 0) { for (int i = 0; i < group.views.Count; ++i) { var view = group.views[i]; var viewMatrix = Matrix4x4.TRS(view.position, view.rotation, Vector3.one).inverse; // Invert Z for metal/openGL if (SystemInfo.usesReversedZBuffer) { viewMatrix.SetRow(2, -viewMatrix.GetRow(2)); } viewMatrices.Add(viewMatrix); Matrix4x4 projectionMatrix; Plane[] frustumPlanes; if (view.perspective) { projectionMatrix = Matrix4x4.Perspective( view.fov, view.aspect, view.minRange, view.maxRange ); viewPositions.Add(view.position); frustumPlanes = MathUtils.GetFrustumPlanesPerspective(view.position, view.rotation, view.fov, view.aspect, view.minRange, view.maxRange); } else { projectionMatrix = Matrix4x4.Ortho( -view.size * view.aspect, view.size * view.aspect, -view.size, view.size, view.minRange, view.maxRange ); viewDirections.Add(view.rotation * Vector3.forward); frustumPlanes = MathUtils.GetFrustumPlanesOrtho(view.position, view.rotation, view.size, view.aspect, view.minRange, view.maxRange); } projectionMatrices.Add(projectionMatrix); viewFrustums.Add(frustumPlanes); } } else { if (canRemoveTubesAndPins || canRemoveKnobs || canRemoveCompletelyInvisible || cullBackfaces) { Debug.LogError("No views specified for backface culling and geometry removal. Disabling!"); canRemoveTubesAndPins = false; canRemoveKnobs = false; canRemoveCompletelyInvisible = false; cullBackfaces = false; } } if (instances.Count > 16777215) { Debug.LogError($"Group {group.groupName} contains too many meshes. Some meshes will not be optimized correctly. Please split the group into multiple groups."); } AnalyzeMeshes(viewMatrices, projectionMatrices, viewPositions, viewDirections, viewFrustums, instances, partMeshCount, partSurfaceMeshCount, canRemoveTubesAndPins, canRemoveKnobs, canRemoveCompletelyInvisible, group.importSettings.lod); // Combine instances to a single mesh for (int i = 0; i < instances.Count; ++i) { if (i % 200 == 0) { EditorUtility.DisplayProgressBar("Processing", "Combining part meshes.", 0.2f + (float)i / instances.Count * 0.05f); } Mesh source = instances[i].mesh; if (source == null) { continue; } Matrix4x4 matrix = instances[i].matrix; Material material = instances[i].material; int subMesh = instances[i].transparent ? (instances[i].type == MeshType.Knob ? 3 : 2) : instances[i].pixelKnobCount >= knobNormalMapThreshold ? 1 : 0; MeshHelper mh = new MeshHelper(source); mh.Transform(matrix); // Cull backfaces if (cullBackfaces) { for (int t = 0; t < mh.triangles.Count; t += 3) { int v0 = mh.triangles[t]; int v1 = mh.triangles[t + 1]; int v2 = mh.triangles[t + 2]; Vector3 triCen = groupMatrix.MultiplyPoint((mh.vertices[v0] + mh.vertices[v1] + mh.vertices[v2]) / 3); Vector3 triNor = groupMatrix.MultiplyVector(mh.normals[v0] + mh.normals[v1] + mh.normals[v2]);//.normalized; bool anyInView = false; for (int v = 0; v < viewPositions.Count; ++v) { anyInView |= (Vector3.Dot(viewPositions[v] - triCen, triNor) >= 0); } for (int v = 0; v < viewDirections.Count; ++v) { anyInView |= (Vector3.Dot(viewDirections[v], triNor) <= 0); } if (!anyInView) { mh.triangles[t] = -1; mh.triangles[t + 1] = -1; mh.triangles[t + 2] = -1; } } mh.triangles.RemoveAll((obj) => obj < 0); mh.RemoveUnusedVertices(); } // culled completely? if (mh.vertices.Count == 0) { partMeshCount[instances[i].part]--; continue; } meshCount.y++; triCount.y += mh.triangles.Count / 3; vertCount.y += mh.vertices.Count; // Store color in vertices mh.SetColor(material); // Randomize normals if (randomizeNormals) { mh.AddNormalNoise(); } meshHelpers[subMesh].Combine(mh); mh = null; } EditorUtility.DisplayProgressBar("Processing", "Building new meshes.", 0.3f); for (int i = 0; i < meshHelpers.Length; ++i) { if (meshHelpers[i].vertices.Count > 0) { GameObject target = group.gameObject; if (i > 0) { target = new GameObject(group.name + "_subMesh" + i); Undo.RegisterCreatedObjectUndo(target, "Create sub mesh"); target.transform.SetParent(group.transform, false); } MeshFilter mf = Undo.AddComponent <MeshFilter>(target); MeshRenderer mr = Undo.AddComponent <MeshRenderer>(target); Mesh m = new Mesh(); meshHelpers[i].ToMesh(m, group.importSettings.lightmapped); // Need tangents? if (i > 0) { m.RecalculateTangents(); } // Make static. target.isStatic = true; if (group.importSettings.lightmapped) { mr.receiveGI = ReceiveGI.Lightmaps; } else { mr.receiveGI = ReceiveGI.LightProbes; } mf.sharedMesh = m; switch (i) { case 0: { PartUtility.StoreOptimizedMesh(m, group.parentName + "_" + group.groupName + "_Optimized.asset"); mr.sharedMaterial = AssetDatabase.LoadAssetAtPath <Material>("Packages/com.unity.lego.modelimporter/Materials/LEGO_VertexColor.mat"); break; } case 1: { PartUtility.StoreOptimizedMesh(m, group.parentName + "_" + group.groupName + "_Optimized_NormalMap.asset"); mr.sharedMaterial = AssetDatabase.LoadAssetAtPath <Material>("Packages/com.unity.lego.modelimporter/Materials/LEGO_VertexColor_NormalMap.mat"); break; } case 2: { PartUtility.StoreOptimizedMesh(m, group.parentName + "_" + group.groupName + "_Optimized_Transparent.asset"); mr.sharedMaterial = AssetDatabase.LoadAssetAtPath <Material>("Packages/com.unity.lego.modelimporter/Materials/LEGO_VertexColor_Transparent.mat"); break; } case 3: { PartUtility.StoreOptimizedMesh(m, group.parentName + "_" + group.groupName + "_Optimized_Transparent_NormalMap.asset"); mr.sharedMaterial = AssetDatabase.LoadAssetAtPath <Material>("Packages/com.unity.lego.modelimporter/Materials/LEGO_VertexColor_Transparent_NormalMap.mat"); break; } } } } } // Remove decoration surfaces for non-legacy parts that have no surface meshes left. progress = 0; foreach (var entry in partSurfaceMeshCount) { if (progress++ % 200 == 0) { EditorUtility.DisplayProgressBar("Processing", "Removing unneeded decorations.", 0.3f + (float)progress / partSurfaceMeshCount.Count * 0.25f); } if (!entry.Key.legacy && entry.Value == 0) { var decorationSurfaces = entry.Key.transform.Find("DecorationSurfaces"); if (decorationSurfaces) { Undo.DestroyObjectImmediate(decorationSurfaces.gameObject); } } } // Remove parts that have no meshes left. progress = 0; foreach (var entry in partMeshCount) { if (progress++ % 200 == 0) { EditorUtility.DisplayProgressBar("Processing", "Removing empty parts.", 0.55f + (float)progress / partMeshCount.Count * 0.25f); } if (entry.Value == 0) { entry.Key.brick.parts.Remove(entry.Key); // If no parts are left, remove the brick. if (entry.Key.brick.parts.Count == 0) { Undo.DestroyObjectImmediate(entry.Key.brick.gameObject); } else { Undo.DestroyObjectImmediate(entry.Key.gameObject); } } } // Collapse remaining box colliders. if (collapseCols) { // FIXME Move to proper constant. var colliderSizeBias = 0.02f; var epsilon = 0.001f; var allColliders = group.GetComponentsInChildren <BoxCollider>(); // Filter out colliders that are part of connectivity features. var partColliders = allColliders.Where((c) => c.gameObject.layer != LayerMask.NameToLayer(Connection.connectivityReceptorLayerName) && c.gameObject.layer != LayerMask.NameToLayer(Connection.connectivityConnectorLayerName)).ToArray(); boxColliderCount.x = partColliders.Length; boxColliderCount.y = partColliders.Length; bool[] colDeleted = new bool[partColliders.Length]; var collapseHappened = true; var iterationCount = 0; while (collapseHappened) { collapseHappened = false; iterationCount++; for (var i = 0; i < partColliders.Length; ++i) { if (i % 200 == 0) { EditorUtility.DisplayProgressBar("Processing", "Collapsing colliders - iteration " + iterationCount + ".", 0.8f + (float)i / partColliders.Length * 0.05f); } if (colDeleted[i]) { continue; } for (var j = i + 1; j < partColliders.Length; ++j) { if (colDeleted[j]) { continue; } var colliderA = partColliders[i]; var colliderB = partColliders[j]; // Check that spaces match up. var colliderBRotationInALocalSpace = Quaternion.Inverse(colliderA.transform.rotation) * colliderB.transform.rotation; var euler = colliderBRotationInALocalSpace.eulerAngles; if (Mathf.Abs(Mathf.Round(euler.x / 90.0f) - euler.x / 90.0f) < epsilon && Mathf.Abs(Mathf.Round(euler.y / 90.0f) - euler.y / 90.0f) < epsilon && Mathf.Abs(Mathf.Round(euler.z / 90.0f) - euler.z / 90.0f) < epsilon) { // Check that centers match up. var colliderBCenterInColliderALocalSpace = colliderA.transform.InverseTransformPoint(colliderB.transform.TransformPoint(colliderB.center)); var centerDiff = colliderBCenterInColliderALocalSpace - colliderA.center; var axisToMatch = centerDiff.MajorAxis(); var centerProjectedToAxis = centerDiff.SnapToMajorAxis() * centerDiff.magnitude; if ((centerDiff - centerProjectedToAxis).sqrMagnitude < 0.01f) { //Debug.Log(GetGameObjectPath(colliderA.gameObject) + colliderA.transform.GetSiblingIndex() + "\n" + GetGameObjectPath(colliderB.gameObject) + colliderB.transform.GetSiblingIndex()); //Debug.Log("Matched centers on " + axisToMatch); // Check that size match up. var colliderBSizeInColliderALocalSpace = colliderA.transform.InverseTransformVector(colliderB.transform.TransformVector(colliderB.size)).Abs(); //Debug.Log("Collider b size in collider a local space: " + colliderBSizeInColliderALocalSpace); var otherAxis1 = (axisToMatch + 1) % 3; var otherAxis2 = (axisToMatch + 2) % 3; if (Mathf.Abs(centerDiff.magnitude - (colliderA.size[axisToMatch] + colliderBSizeInColliderALocalSpace[axisToMatch] + 2.0f * colliderSizeBias) * 0.5f) < epsilon) { //Debug.Log("Matched on axis length"); if (Mathf.Abs(colliderA.size[otherAxis1] - colliderBSizeInColliderALocalSpace[otherAxis1]) < epsilon && Mathf.Abs(colliderA.size[otherAxis2] - colliderBSizeInColliderALocalSpace[otherAxis2]) < epsilon) { //Debug.Log("Matched other axes"); // Merge collider B into collider A. colliderA.center += centerDiff.normalized * (colliderBSizeInColliderALocalSpace[axisToMatch] + colliderSizeBias) * 0.5f; var newSize = colliderA.size; newSize[axisToMatch] += colliderBSizeInColliderALocalSpace[axisToMatch] + colliderSizeBias; colliderA.size = newSize; // Update part and destroy collider game object. Empty parent Colliders game objects will be removed during part clean-up. var part = colliderB.GetComponentInParent <Part>(); part.colliders.Remove(colliderB); Undo.DestroyObjectImmediate(colliderB.gameObject); colDeleted[j] = true; boxColliderCount.y--; // Note that a change was made, and run over colliders again when done. collapseHappened = true; } } } } } } } } if (collapseMesh || collapseCols) { // Collect all remaining parts and clean them up. progress = 0; var parts = group.GetComponentsInChildren <Part>(); foreach (var part in parts) { if (progress++ % 200 == 0) { EditorUtility.DisplayProgressBar("Processing", "Cleaning up remaining parts.", 0.85f + (float)progress / parts.Length * 0.15f); } CleanupPartGeometryTransforms(part); } } EditorUtility.ClearProgressBar(); }
/// <summary> /// Instantiate game objects for each brick in an LXFML-file /// </summary> /// <param name="lxfml">The LXFML-file</param> /// <param name="colliders">Add colliders to part</param> /// <param name="connectivity">Add connectivity to part</param> /// <param name="isStatic">Make the part static</param> /// <param name="lightmapped">Instantiate meshes with or without lightmap UVs</param> /// <param name="randomizeRotation">Slightly rotate rotation of part</param> /// <param name="preferLegacy">Choose legacy meshes if available</param> /// <param name="lod">Instantiate meshes of a certain LOD</param> /// <param name="resultBricks">Dictionary that contains brick component, using refID as key</param> /// <param name="groupNumber">If non-negative, only instantiate bricks from the specified group number</param> public static void InstantiateModelBricks(LXFMLDoc lxfml, DictionaryIntToModelGroupImportSettings importSettings, ref Dictionary <int, Brick> resultBricks, int groupNumber = -1) { for (var i = 0; i < lxfml.bricks.Count; ++i) { if (i % 200 == 0) { EditorUtility.DisplayProgressBar("Importing", "Creating bricks.", ((float)i / lxfml.bricks.Count) * 0.7f); } var brick = lxfml.bricks[i]; var group = FindGroup(lxfml, brick); // Discard bricks from other groups if group number is specified. if (groupNumber >= 0 && group != null && group.number != groupNumber) { continue; } // Determine whether or not to be static and to generate light map UVs. var brickStatic = (group != null ? importSettings[group.number].isStatic : false); var brickLightmapped = brickStatic && (group != null ? importSettings[group.number].lightmapped : false); var brickLod = (group != null ? importSettings[group.number].lod : 0); var brickGO = new GameObject(brick.designId, typeof(Brick)); var brickComp = brickGO.GetComponent <Brick>(); Undo.RegisterCreatedObjectUndo(brickGO, "Brick"); foreach (var part in brick.parts) { GameObject partToInstantiate = null; var partExistenceResult = PartUtility.UnpackPart(part.partDesignId, brickLightmapped, group != null ? importSettings[group.number].preferLegacy : false, brickLod); if (partExistenceResult.existence != PartUtility.PartExistence.None) { // FIXME Make a note of changed design ids. partToInstantiate = PartUtility.LoadPart(partExistenceResult.designID, brickLightmapped, partExistenceResult.existence == PartUtility.PartExistence.Legacy, brickLod); } if (partToInstantiate == null) { Debug.LogError("Missing part FBX -> " + partExistenceResult.designID); continue; } var partGO = Object.Instantiate(partToInstantiate); partGO.name = partToInstantiate.name; // Assign legacy, material IDs and set up references. var partComp = partGO.AddComponent <Part>(); partComp.designID = Convert.ToInt32(part.partDesignId); partComp.legacy = partExistenceResult.existence == PartUtility.PartExistence.Legacy; foreach (var material in part.materials) { partComp.materialIDs.Add(material.colorId); } partComp.brick = brickComp; brickComp.parts.Add(partComp); if (partExistenceResult.existence == PartUtility.PartExistence.New) { // FIXME Handle normal mapped model. InstantiateKnobsAndTubes(partComp, brickLightmapped, brickLod); } // Create collider and connectivity information. var brickColliders = (group != null ? importSettings[group.number].colliders : false); var brickConnectivity = brickColliders && (group != null ? importSettings[group.number].connectivity : false); if (brickColliders) { GameObject collidersToInstantiate = null; var collidersAvailable = PartUtility.UnpackCollidersForPart(partExistenceResult.designID); if (collidersAvailable) { collidersToInstantiate = PartUtility.LoadCollidersPrefab(partExistenceResult.designID); } if (collidersToInstantiate == null && partExistenceResult.existence != PartUtility.PartExistence.Legacy) { Debug.LogError("Missing part collider information -> " + partExistenceResult.designID); } if (collidersToInstantiate) { var collidersGO = Object.Instantiate(collidersToInstantiate); collidersGO.name = "Colliders"; collidersGO.transform.SetParent(partGO.transform, false); var colliderComps = collidersGO.GetComponentsInChildren <Collider>(); partComp.colliders.AddRange(colliderComps); } } if (brickConnectivity) { GameObject connectivityToInstantiate = null; var connectivityAvailable = PartUtility.UnpackConnectivityForPart(partExistenceResult.designID); if (connectivityAvailable) { connectivityToInstantiate = PartUtility.LoadConnectivityPrefab(partExistenceResult.designID); } if (connectivityToInstantiate == null && partExistenceResult.existence != PartUtility.PartExistence.Legacy) { Debug.LogError("Missing part connectivity information -> " + partExistenceResult.designID); } if (connectivityToInstantiate) { var connectivityGO = Object.Instantiate(connectivityToInstantiate); connectivityGO.name = "Connectivity"; connectivityGO.transform.SetParent(partGO.transform, false); var connectivityComp = connectivityGO.GetComponent <Connectivity>(); partComp.connectivity = connectivityComp; brickComp.totalBounds.Encapsulate(connectivityComp.extents); connectivityComp.part = partComp; foreach (var field in connectivityComp.connectionFields) { foreach (var connection in field.connections) { MatchConnectionWithKnob(connection, partComp.knobs); MatchConnectionWithTubes(connection, partComp.tubes); } } } } SetMaterials(partComp, part.materials, partExistenceResult.existence == PartUtility.PartExistence.Legacy); SetDecorations(partComp, part.decorations, partExistenceResult.existence == PartUtility.PartExistence.Legacy); SetStaticAndGIParams(partGO, brickStatic, brickLightmapped, true); // Set Position & Rotation SetPositionRotation(partGO, part); if (group != null ? importSettings[group.number].randomizeRotation : false) { RandomizeRotation(partComp, brickConnectivity); } // If first part, place brick at same position. if (brickGO.transform.childCount == 0) { brickGO.transform.position = partGO.transform.position; brickGO.transform.rotation = partGO.transform.rotation; brickGO.transform.localScale = Vector3.one; } partGO.transform.SetParent(brickGO.transform, true); if (!brickConnectivity) { var worldBounds = ComputeBounds(partGO.transform); worldBounds.SetMinMax(brickComp.transform.InverseTransformPoint(worldBounds.min), brickComp.transform.InverseTransformPoint(worldBounds.max)); brickComp.totalBounds.Encapsulate(worldBounds); } } // If all parts were missing, discard brick. if (brickGO.transform.childCount == 0) { Undo.DestroyObjectImmediate(brickGO); continue; } SetStaticAndGIParams(brickGO, brickStatic, brickLightmapped); // Assign uuid brickComp.designID = Convert.ToInt32(brick.designId); brickComp.uuid = brick.uuid; // Add LEGOAsset component. brickGO.AddComponent <LEGOAsset>(); resultBricks[brick.refId] = brickComp; } }
private static void AddPartToBrick(Brick brick, string ldrawID, Matrix4x4 transformation, string materialID, string fullName) { // Report if material is missing. var materialExistence = MaterialUtility.CheckIfMaterialExists(materialID); if (materialExistence == MaterialUtility.MaterialExistence.None) { missingMaterials.Add($"Brick ID {ldrawID}\tMaterial ID {materialID}"); } else if (materialExistence == MaterialUtility.MaterialExistence.Legacy) { legacyMaterials.Add($"Brick ID {ldrawID}\tMaterial ID {materialID}"); } // 1. Peel off anything from 'p' onwards and check if it includes a number. (Pattern constant + pattern sequential #) var patternSplit = Regex.Split(ldrawID, "(p[a-z]*[0-9]*)"); // 2. If something was peeled off, make a note of it. We cannot map the pattern sequential id to a decoration imageId and we also don't know the surfaceName. if (patternSplit.Length > 1) { //Debug.Log("PATTERN: " + patternSplit[0] + patternSplit[1]); missingDecorations.Add($"Brick ID {fullName}"); } // 3. Peel off anything from 'd' onwards and check if it includes a number. (Sticker + sticker number) var stickerSplit = Regex.Split(patternSplit[0], "(d[0-9]+)"); // 4. If something was peeled off, make a note of it. if (stickerSplit.Length > 1) { //Debug.Log("STICKER: " + stickerSplit[0] + stickerSplit[1]); missingStickers.Add($"Brick ID {fullName}"); } // 5. Reassemble remaining id + ".dat" and use as ldrawID. var designID = stickerSplit[0]; ldrawID = designID + ".dat"; // Apply mapping to another designID. if (LDrawBrickToLEGOBrick.ContainsKey(ldrawID)) { designID = LDrawBrickToLEGOBrick[ldrawID]; } // 6. If a mesh does not exist with exact designID, make a note of it and try to peel off any trailing letters. var partExistenceResult = PartUtility.CheckIfPartExists(designID); if (partExistenceResult.existence == PartUtility.PartExistence.None) { var versionSplit = Regex.Split(designID, "([a-z]+)"); designID = versionSplit[0]; // 6b. If there was something to peel off, look again with new designID. if (versionSplit.Length > 1) { partExistenceResult = PartUtility.CheckIfPartExists(designID); if (partExistenceResult.existence == PartUtility.PartExistence.None) { // Missing part. missingParts.Add($"Brick ID {fullName}"); } else { // Changed part. changedParts.Add($"Brick ID {fullName}\tChanges to {designID}"); // Legacy part. if (partExistenceResult.existence == PartUtility.PartExistence.Legacy) { // FIXME Check if colliders and connectivity info are available. legacyParts.Add($"Brick ID {fullName}"); } } } else { // Missing part. missingParts.Add($"Brick ID {fullName}"); } } else if (partExistenceResult.existence == PartUtility.PartExistence.Legacy) { // Legacy part. // FIXME Check if colliders and connectivity info are available. legacyParts.Add($"Brick ID {fullName}"); } // Reconstruct potentially changed ldrawID. ldrawID = designID + ".dat"; // Apply transformation for ldrawID. if (LDrawBrickToTransformation.ContainsKey(ldrawID)) { transformation *= LDrawBrickToTransformation[ldrawID]; } else if (partExistenceResult.existence != PartUtility.PartExistence.None) { missingTransformations.Add($"Brick ID {fullName}"); } var part = new Part() { transformation = transformation, designID = designID, materialID = materialID }; brick.parts.Add(part); // Assign design ID of part to brick. This is incorrect for multi-part bricks but we do not know the correct design ID. brick.designID = designID; }
public static void ConvertVersion_0_To_1(Connectivity connectivity) { Debug.Log($"Updating Connectivity 0 -> 1 on {connectivity.name}"); PartUtility.UnpackConnectivityForPart(connectivity.name, true); }
private static bool UpdateConnections(Brick[] bricks) { var updated = false; foreach (var brick in bricks) { if (PrefabUtility.IsPartOfPrefabInstance(brick)) { continue; } foreach (var part in brick.parts) { var designID = part.designID.ToString(); if (!PartUtility.CheckIfConnectivityForPartIsUnpacked(designID)) { PartUtility.UnpackConnectivityForPart(designID); } var connectivity = part.connectivity; if (!connectivity) { // Unsupported or legacy. continue; } if (connectivity.version == currentVersion) { // Already up to date. continue; } var connectivityToInstantiate = PartUtility.LoadConnectivityPrefab(designID); if (connectivityToInstantiate) { GameObject.DestroyImmediate(connectivity.gameObject, true); var connectivityGO = UnityEngine.Object.Instantiate(connectivityToInstantiate); connectivityGO.name = "Connectivity"; connectivityGO.transform.SetParent(part.transform, false); var connectivityComp = connectivityGO.GetComponent <Connectivity>(); part.connectivity = connectivityComp; part.brick.totalBounds.Encapsulate(connectivityComp.extents); connectivityComp.part = part; updated = true; foreach (var tube in part.tubes) { tube.connections.Clear(); tube.field = null; } foreach (var knob in part.knobs) { knob.field = null; knob.connectionIndex = -1; } foreach (var field in connectivityComp.connectionFields) { foreach (var connection in field.connections) { ModelImporter.MatchConnectionWithKnob(connection, part.knobs); ModelImporter.MatchConnectionWithTubes(connection, part.tubes); } } } } } return(updated); }