public override bool AddDeleteGameObjectsByID(GameObject[] gos, int[] deleteGOinstanceIDs, bool disableRendererInSource = true) { //Profile.Start//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects1"); //PART 1 ==== Validate if (_usingTemporaryTextureBakeResult && gos != null && gos.Length > 0) { _textureBakeResults = null; _usingTemporaryTextureBakeResult = false; } //if all objects use the same material we can create a temporary _textureBakeResults if (_textureBakeResults == null && gos != null && gos.Length > 0 && gos[0] != null) { if (!_CreateTemporaryTextrueBakeResult(gos)) { return(false); } } if (!_validate(gos, deleteGOinstanceIDs)) { return(false); } _distributeAmongBakers(gos, deleteGOinstanceIDs); if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("MB2_MultiMeshCombiner.AddDeleteGameObjects numCombinedMeshes: " + meshCombiners.Count + " added:" + gos + " deleted:" + deleteGOinstanceIDs + " disableRendererInSource:" + disableRendererInSource + " maxVertsPerCombined:" + _maxVertsInMesh); } return(_bakeStep1(gos, deleteGOinstanceIDs, disableRendererInSource)); }
public static Vector2[] GetMeshUV3orUV4(Mesh m, bool get3, MBLogLevel LOG_LEVEL) { Vector2[] uvs; if (get3) { uvs = m.uv3; } else { uvs = m.uv4; } if (uvs.Length == 0) { if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("Mesh " + m + " has no uv" + (get3 ? "3" : "4") + ". Generating"); } uvs = new Vector2[m.vertexCount]; for (int i = 0; i < uvs.Length; i++) { uvs[i] = _HALF_UV; } } return(uvs); }
public static Vector2[] GetMeshUV1s(Mesh m, MBLogLevel LOG_LEVEL) { Vector2[] uv; if (LOG_LEVEL >= MBLogLevel.warn) { MBLog.LogDebug("UV1 does not exist in Unity 5+"); } uv = m.uv; if (uv.Length == 0) { if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("Mesh " + m + " has no uv1s. Generating"); } if (LOG_LEVEL >= MBLogLevel.warn) { Debug.LogWarning("Mesh " + m + " didn't have uv1s. Generating uv1s."); } uv = new Vector2[m.vertexCount]; for (int i = 0; i < uv.Length; i++) { uv[i] = _HALF_UV; } } return(uv); }
Rect[] _GetRects(List <Vector2> imgWidthHeights, int maxDimension, int padding, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY, out int outW, out int outH, int recursionDepth) { if (LOG_LEVEL >= MBLogLevel.debug) { Debug.Log(String.Format("_GetRects numImages={0}, maxDimension={1}, padding={2}, minImageSizeX={3}, minImageSizeY={4}, masterImageSizeX={5}, masterImageSizeY={6}, recursionDepth={7}", imgWidthHeights.Count, maxDimension, padding, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, recursionDepth)); } if (recursionDepth > 10) { Debug.LogError("Maximum recursion depth reached. Couldn't find packing for these textures."); outW = 0; outH = 0; return(new Rect[0]); } float area = 0; int maxW = 0; int maxH = 0; Image[] imgsToAdd = new Image[imgWidthHeights.Count]; for (int i = 0; i < imgsToAdd.Length; i++) { Image im = imgsToAdd[i] = new Image(i, (int)imgWidthHeights[i].x, (int)imgWidthHeights[i].y, padding, minImageSizeX, minImageSizeY); area += im.w * im.h; maxW = Mathf.Max(maxW, im.w); maxH = Mathf.Max(maxH, im.h); } if ((float)maxH / (float)maxW > 2) { if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("Using height Comparer"); } Array.Sort(imgsToAdd, new ImageHeightComparer()); } else if ((float)maxH / (float)maxW < .5) { if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("Using width Comparer"); } Array.Sort(imgsToAdd, new ImageWidthComparer()); } else { if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("Using area Comparer"); } Array.Sort(imgsToAdd, new ImageAreaComparer()); } //explore the space to find a resonably efficient packing int sqrtArea = (int)Mathf.Sqrt(area); int idealAtlasW; int idealAtlasH; if (doPowerOfTwoTextures) { idealAtlasW = idealAtlasH = RoundToNearestPositivePowerOfTwo(sqrtArea); if (maxW > idealAtlasW) { idealAtlasW = CeilToNearestPowerOfTwo(idealAtlasW); } if (maxH > idealAtlasH) { idealAtlasH = CeilToNearestPowerOfTwo(idealAtlasH); } } else { idealAtlasW = sqrtArea; idealAtlasH = sqrtArea; if (maxW > sqrtArea) { idealAtlasW = maxW; idealAtlasH = Mathf.Max(Mathf.CeilToInt(area / maxW), maxH); } if (maxH > sqrtArea) { idealAtlasW = Mathf.Max(Mathf.CeilToInt(area / maxH), maxW); idealAtlasH = maxH; } } if (idealAtlasW == 0) { idealAtlasW = 1; } if (idealAtlasH == 0) { idealAtlasH = 1; } int stepW = (int)(idealAtlasW * .15f); int stepH = (int)(idealAtlasH * .15f); if (stepW == 0) { stepW = 1; } if (stepH == 0) { stepH = 1; } int numWIterations = 2; int steppedWidth = idealAtlasW; int steppedHeight = idealAtlasH; while (numWIterations >= 1 && steppedHeight < sqrtArea * 1000) { bool successW = false; numWIterations = 0; steppedWidth = idealAtlasW; while (!successW && steppedWidth < sqrtArea * 1000) { ProbeResult pr = new ProbeResult(); if (LOG_LEVEL >= MBLogLevel.trace) { Debug.Log("Probing h=" + steppedHeight + " w=" + steppedWidth); } if (Probe(imgsToAdd, steppedWidth, steppedHeight, area, maxDimension, pr)) { successW = true; if (bestRoot == null) { bestRoot = pr; } else if (pr.GetScore(doPowerOfTwoTextures) > bestRoot.GetScore(doPowerOfTwoTextures)) { bestRoot = pr; } } else { numWIterations++; steppedWidth = StepWidthHeight(steppedWidth, stepW, maxDimension); if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("increasing Width h=" + steppedHeight + " w=" + steppedWidth); } } } steppedHeight = StepWidthHeight(steppedHeight, stepH, maxDimension); if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("increasing Height h=" + steppedHeight + " w=" + steppedWidth); } } outW = 0; outH = 0; if (doPowerOfTwoTextures) { outW = Mathf.Min(CeilToNearestPowerOfTwo(bestRoot.w), maxDimension); outH = Mathf.Min(CeilToNearestPowerOfTwo(bestRoot.h), maxDimension); if (outH < outW / 2) { outH = outW / 2; //smaller dim can't be less than half larger } if (outW < outH / 2) { outW = outH / 2; } } else { outW = bestRoot.w; outH = bestRoot.h; } if (bestRoot == null) { return(null); } if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("Best fit found: atlasW=" + outW + " atlasH" + outH + " w=" + bestRoot.w + " h=" + bestRoot.h + " efficiency=" + bestRoot.efficiency + " squareness=" + bestRoot.squareness + " fits in max dimension=" + bestRoot.fitsInMaxSize); } List <Image> images = new List <Image>(); flattenTree(bestRoot.root, images); images.Sort(new ImgIDComparer()); if (images.Count != imgsToAdd.Length) { Debug.LogError("Result images not the same lentgh as source"); } //scale images if too large int newMinSizeX = minImageSizeX; int newMinSizeY = minImageSizeY; bool redoPacking = false; float padX = (float)padding / (float)outW; if (bestRoot.w > maxDimension) { //float minSizeX = ((float)minImageSizeX + 1) / maxDimension; padX = (float)padding / (float)maxDimension; float scaleFactor = (float)maxDimension / (float)bestRoot.w; if (LOG_LEVEL >= MBLogLevel.warn) { Debug.LogWarning("Packing exceeded atlas width shrinking to " + scaleFactor); } for (int i = 0; i < images.Count; i++) { Image im = images[i]; if (im.w * scaleFactor < masterImageSizeX) //check if small images will be rounded too small. If so need to redo packing forcing a larger min size { if (LOG_LEVEL >= MBLogLevel.debug) { Debug.Log("Small images are being scaled to zero. Will need to redo packing with larger minTexSizeX."); } redoPacking = true; newMinSizeX = Mathf.CeilToInt(minImageSizeX / scaleFactor); } int right = (int)((im.x + im.w) * scaleFactor); im.x = (int)(scaleFactor * im.x); im.w = right - im.x; } outW = maxDimension; } float padY = (float)padding / (float)outH; if (bestRoot.h > maxDimension) { //float minSizeY = ((float)minImageSizeY + 1) / maxDimension; padY = (float)padding / (float)maxDimension; float scaleFactor = (float)maxDimension / (float)bestRoot.h; if (LOG_LEVEL >= MBLogLevel.warn) { Debug.LogWarning("Packing exceeded atlas height shrinking to " + scaleFactor); } for (int i = 0; i < images.Count; i++) { Image im = images[i]; if (im.h * scaleFactor < masterImageSizeY) //check if small images will be rounded too small. If so need to redo packing forcing a larger min size { if (LOG_LEVEL >= MBLogLevel.debug) { Debug.Log("Small images are being scaled to zero. Will need to redo packing with larger minTexSizeY."); } redoPacking = true; newMinSizeY = Mathf.CeilToInt(minImageSizeY / scaleFactor); } int bottom = (int)((im.y + im.h) * scaleFactor); im.y = (int)(scaleFactor * im.y); im.h = bottom - im.y; } outH = maxDimension; } Rect[] rs; if (!redoPacking) { rs = new Rect[images.Count]; for (int i = 0; i < images.Count; i++) { Image im = images[i]; Rect r = rs[i] = new Rect((float)im.x / (float)outW + padX, (float)im.y / (float)outH + padY, (float)im.w / (float)outW - padX * 2f, (float)im.h / (float)outH - padY * 2f); if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("Image: " + i + " imgID=" + im.imgId + " x=" + r.x * outW + " y=" + r.y * outH + " w=" + r.width * outW + " h=" + r.height * outH + " padding=" + padding); } } } else { if (LOG_LEVEL >= MBLogLevel.debug) { Debug.Log("==================== REDOING PACKING ================"); } bestRoot = null; rs = _GetRects(imgWidthHeights, maxDimension, padding, newMinSizeX, newMinSizeY, masterImageSizeX, masterImageSizeY, out outW, out outH, recursionDepth + 1); } if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("Done GetRects"); } return(rs); }
bool Probe(Image[] imgsToAdd, int idealAtlasW, int idealAtlasH, float imgArea, int maxAtlasDim, ProbeResult pr) { Node root = new Node(); root.r = new PixRect(0, 0, idealAtlasW, idealAtlasH); for (int i = 0; i < imgsToAdd.Length; i++) { Node n = root.Insert(imgsToAdd[i], false); if (n == null) { return(false); } else if (i == imgsToAdd.Length - 1) { int usedW = 0; int usedH = 0; GetExtent(root, ref usedW, ref usedH); float efficiency, squareness; bool fitsInMaxDim; if (doPowerOfTwoTextures) { int atlasW = Mathf.Min(CeilToNearestPowerOfTwo(usedW), maxAtlasDim); int atlasH = Mathf.Min(CeilToNearestPowerOfTwo(usedH), maxAtlasDim); if (atlasH < atlasW / 2) { atlasH = atlasW / 2; } if (atlasW < atlasH / 2) { atlasW = atlasH / 2; } fitsInMaxDim = usedW <= maxAtlasDim && usedH <= maxAtlasDim; float scaleW = Mathf.Max(1f, ((float)usedW) / maxAtlasDim); float scaleH = Mathf.Max(1f, ((float)usedH) / maxAtlasDim); float atlasArea = atlasW * scaleW * atlasH * scaleH; //area if we scaled it up to something large enough to contain images efficiency = 1f - (atlasArea - imgArea) / atlasArea; squareness = 1f; //don't care about squareness in power of two case } else { efficiency = 1f - (usedW * usedH - imgArea) / (usedW * usedH); if (usedW < usedH) { squareness = (float)usedW / (float)usedH; } else { squareness = (float)usedH / (float)usedW; } fitsInMaxDim = usedW <= maxAtlasDim && usedH <= maxAtlasDim; } pr.Set(usedW, usedH, root, fitsInMaxDim, efficiency, squareness); if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("Probe success efficiency w=" + usedW + " h=" + usedH + " e=" + efficiency + " sq=" + squareness + " fits=" + fitsInMaxDim); } return(true); } } Debug.LogError("Should never get here."); return(false); }
bool _bakeStep1(GameObject[] gos, int[] deleteGOinstanceIDs, bool disableRendererInSource) { //Profile.End//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects2.2"); //Profile.Start//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects3"); //PART 3 ==== Add delete meshes from combined for (int i = 0; i < meshCombiners.Count; i++) { CombinedMesh cm = meshCombiners[i]; if (cm.combinedMesh.targetRenderer == null) { cm.combinedMesh.resultSceneObject = _resultSceneObject; cm.combinedMesh.BuildSceneMeshObject(gos, true); if (_LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("BuildSO combiner {0} goID {1} targetRenID {2} meshID {3}", i, cm.combinedMesh.targetRenderer.gameObject.GetInstanceID(), cm.combinedMesh.targetRenderer.GetInstanceID(), cm.combinedMesh.GetMesh().GetInstanceID()); } } else { if (cm.combinedMesh.targetRenderer.transform.parent != resultSceneObject.transform) { Debug.LogError("targetRender objects must be children of resultSceneObject"); return(false); } } if (cm.gosToAdd.Count > 0 || cm.gosToDelete.Count > 0) { cm.combinedMesh.AddDeleteGameObjectsByID(cm.gosToAdd.ToArray(), cm.gosToDelete.ToArray(), disableRendererInSource); if (_LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("Baked combiner {0} obsAdded {1} objsRemoved {2} goID {3} targetRenID {4} meshID {5}", i, cm.gosToAdd.Count, cm.gosToDelete.Count, cm.combinedMesh.targetRenderer.gameObject.GetInstanceID(), cm.combinedMesh.targetRenderer.GetInstanceID(), cm.combinedMesh.GetMesh().GetInstanceID()); } } Renderer r = cm.combinedMesh.targetRenderer; Mesh m = cm.combinedMesh.GetMesh(); if (r is MeshRenderer) { MeshFilter mf = r.gameObject.GetComponent <MeshFilter>(); mf.sharedMesh = m; } else { SkinnedMeshRenderer smr = (SkinnedMeshRenderer)r; smr.sharedMesh = m; } } for (int i = 0; i < meshCombiners.Count; i++) { CombinedMesh cm = meshCombiners[i]; for (int j = 0; j < cm.gosToDelete.Count; j++) { obj2MeshCombinerMap.Remove(cm.gosToDelete[j]); } } for (int i = 0; i < meshCombiners.Count; i++) { CombinedMesh cm = meshCombiners[i]; for (int j = 0; j < cm.gosToAdd.Count; j++) { obj2MeshCombinerMap.Add(cm.gosToAdd[j].GetInstanceID(), cm); } if (cm.gosToAdd.Count > 0 || cm.gosToDelete.Count > 0) { cm.gosToDelete.Clear(); cm.gosToAdd.Clear(); cm.numVertsInListToDelete = 0; cm.numVertsInListToAdd = 0; cm.isDirty = true; } } //Profile.End//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects3"); if (LOG_LEVEL >= MBLogLevel.debug) { string s = "Meshes in combined:"; for (int i = 0; i < meshCombiners.Count; i++) { s += " mesh" + i + "(" + meshCombiners[i].combinedMesh.GetObjectsInCombined().Count + ")\n"; } s += "children in result: " + resultSceneObject.transform.childCount; MBLog.LogDebug(s, LOG_LEVEL); } if (meshCombiners.Count > 0) { return(true); } else { return(false); } }
void _distributeAmongBakers(GameObject[] gos, int[] deleteGOinstanceIDs) { if (gos == null) { gos = empty; } if (deleteGOinstanceIDs == null) { deleteGOinstanceIDs = emptyIDs; } if (resultSceneObject == null) { resultSceneObject = new GameObject("CombinedMesh-" + name); } //PART 2 ==== calculate which bakers to add objects to for (int i = 0; i < meshCombiners.Count; i++) { meshCombiners[i].extraSpace = _maxVertsInMesh - meshCombiners[i].combinedMesh.GetMesh().vertexCount; } //Profile.End//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects1"); //Profile.Start//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects2.1"); //first delete game objects from the existing combinedMeshes keep track of free space for (int i = 0; i < deleteGOinstanceIDs.Length; i++) { CombinedMesh c = null; if (obj2MeshCombinerMap.TryGetValue(deleteGOinstanceIDs[i], out c)) { if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("MB2_MultiMeshCombiner.Removing " + deleteGOinstanceIDs[i] + " from meshCombiner " + meshCombiners.IndexOf(c)); } c.numVertsInListToDelete += c.combinedMesh.GetNumVerticesFor(deleteGOinstanceIDs[i]); //m.vertexCount; c.gosToDelete.Add(deleteGOinstanceIDs[i]); } else { Debug.LogWarning("Object " + deleteGOinstanceIDs[i] + " in the list of objects to delete is not in the combined mesh."); } } for (int i = 0; i < gos.Length; i++) { GameObject go = gos[i]; int numVerts = MeshBakerUtility.GetMesh(go).vertexCount; CombinedMesh cm = null; for (int j = 0; j < meshCombiners.Count; j++) { if (meshCombiners[j].extraSpace + meshCombiners[j].numVertsInListToDelete - meshCombiners[j].numVertsInListToAdd > numVerts) { cm = meshCombiners[j]; if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("MB2_MultiMeshCombiner.Added " + gos[i] + " to combinedMesh " + j, LOG_LEVEL); } break; } } if (cm == null) { cm = new CombinedMesh(maxVertsInMesh, _resultSceneObject, _LOG_LEVEL); _setMBValues(cm.combinedMesh); meshCombiners.Add(cm); if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("MB2_MultiMeshCombiner.Created new combinedMesh"); } } cm.gosToAdd.Add(go); cm.numVertsInListToAdd += numVerts; // obj2MeshCombinerMap.Add(go,cm); } }