/// <summary> /// Puts a voxel in the given position. Takes care of informing neighbour chunks. /// </summary> /// <returns>Returns the affected chunk and voxel index</returns> /// <param name="position">Position.</param> /// <param name="voxel">Voxel.</param> void VoxelPlaceFast(Vector3 position, VoxelDefinition voxelType, out VoxelChunk chunk, out int voxelIndex, Color32 tintColor, float amount = 1f, int rotation = 0) { VoxelSingleSet(position, voxelType, out chunk, out voxelIndex, tintColor); // Apply rotation if (voxelType.allowsTextureRotation) { chunk.voxels [voxelIndex].SetTextureRotation(rotation); } // If it's water, add flood if (voxelType.spreads) { chunk.voxels [voxelIndex].SetWaterLevel((Mathf.CeilToInt(amount * 15f))); AddWaterFlood(ref position, voxelType); } chunk.modified = true; // Update neighbours UpdateChunkRR(chunk); // Triggers event if (OnChunkChanged != null) { OnChunkChanged(chunk); } }
public void SetFastWater(VoxelDefinition type) { this.typeIndex = type.index; this.hasContent = type.hasContent; this.opaque = 2; this._flags = type.height; }
int CreateSeeThroughVoxelDefinition(VoxelDefinition original) { VoxelDefinition clone = Instantiate <VoxelDefinition> (original); clone.name = original.name + " SeeThrough"; clone.renderType = RenderType.Transp6tex; clone.index = 0; clone.doNotSave = true; clone.alpha = original.alpha; clone.hidden = true; clone.canBeCollected = false; clone.textureIndexTop = original.textureIndexTop; clone.textureIndexSide = original.textureIndexSide; clone.textureIndexBottom = original.textureIndexBottom; clone.textureSideIndices = original.textureSideIndices; clone.seeThroughMode = SeeThroughMode.Transparency; clone.colorVariation = original.colorVariation; clone.ignoresRayCast = true; clone.navigatable = original.navigatable; clone.opaque = original.opaque; clone.tintColor = original.tintColor; clone.windAnimation = original.windAnimation; clone.materialBufferIndex = INDICES_BUFFER_TRANSP; AppendVoxelDefinition(clone); return(clone.index); }
protected void CheckFootfalls() { if (!isGrounded && !isInWater) { Vector3 curPos = transform.position; int x = (int)curPos.x; int y = (int)curPos.y; int z = (int)curPos.z; if (x != lastPosX || y != lastPosY || z != lastPosZ) { lastPosX = x; lastPosY = y; lastPosZ = z; VoxelIndex index = env.GetVoxelUnderIndex(curPos, true); if (index.typeIndex != lastVoxelTypeIndex) { lastVoxelTypeIndex = index.typeIndex; if (lastVoxelTypeIndex != 0) { VoxelDefinition vd = index.type; SetFootstepSounds(vd.footfalls, vd.landingSound, vd.jumpSound); if (vd.triggerWalkEvent && OnVoxelWalk != null) { OnVoxelWalk(index.chunk, index.voxelIndex); } CheckDamage(vd); } } } } }
protected override void Init() { seaLevelAlignedWithInt = (waterLevel / (float)maxHeight); beachLevelAlignedWithInt = (waterLevel + 1f) / maxHeight; if (steps != null) { for (int k = 0; k < steps.Length; k++) { if (steps [k].noiseTexture != null) { bool repeated = false; for (int j = 0; j < k - 1; j++) { if (steps [k].noiseTexture == steps [k].noiseTexture) { steps [k].noiseValues = steps [j].noiseValues; steps [k].noiseTextureSize = steps [j].noiseTextureSize; repeated = true; break; } } if (!repeated && (steps [k].noiseTextureSize == 0 || steps [k].noiseValues == null || steps [k].lastTextureLoaded == null || steps [k].noiseTexture != steps [k].lastTextureLoaded)) { steps [k].lastTextureLoaded = steps [k].noiseTexture; steps [k].noiseValues = NoiseTools.LoadNoiseTexture(steps [k].noiseTexture, out steps [k].noiseTextureSize); } } // Validate references if (steps [k].inputIndex0 < 0 || steps [k].inputIndex0 >= steps.Length) { steps [k].inputIndex0 = 0; } if (steps [k].inputIndex1 < 0 || steps [k].inputIndex1 >= steps.Length) { steps [k].inputIndex1 = 0; } } } if (moisture != null && (noiseMoistureTextureSize == 0 || moistureValues == null || lastMoistureTextureLoaded == null || lastMoistureTextureLoaded != moisture)) { lastMoistureTextureLoaded = moisture; moistureValues = NoiseTools.LoadNoiseTexture(moisture, out noiseMoistureTextureSize); } if (waterVoxel == null) { waterVoxel = Resources.Load <VoxelDefinition> ("VoxelPlay/Defaults/Water/VoxelWaterSea"); } paintShore = shoreVoxel != null; if (heightChunkData == null) { heightChunkData = new HeightMapInfo[16 * 16]; } // Ensure voxels are available env.AddVoxelDefinition(shoreVoxel); env.AddVoxelDefinition(waterVoxel); env.AddVoxelDefinition(bedrockVoxel); }
/// <summary> /// Updates water voxel at a given position. /// </summary> /// <returns>Returns the affected chunk and voxel index</returns> void WaterUpdateLevelFast(VoxelChunk chunk, int voxelIndex, int waterLevel, VoxelDefinition vd) { if (waterLevel > 15) { waterLevel = 15; } if (chunk.voxels [voxelIndex].GetWaterLevel() == waterLevel) { return; } if (waterLevel == 0) { chunk.voxels [voxelIndex].Clear(chunk.voxels [voxelIndex].light); } else { if (chunk.voxels [voxelIndex].GetWaterLevel() == 0) { chunk.voxels [voxelIndex].Set(vd); if (vd.lightIntensity > 0) { chunk.AddLightSource(voxelIndex, vd.lightIntensity); } } chunk.voxels [voxelIndex].SetWaterLevel(waterLevel); } chunk.modified = true; }
public void Add(ref Vector3 position, VoxelDefinition waterVoxel, int lifeTime) { if (waterFloodPositions.Contains(position)) { return; } for (int i = last + 1; i < last + WATER_FLOOD_CAPACITY; i++) { int k = i % WATER_FLOOD_CAPACITY; if (nodes [k].lifeTime <= 0) { waterFloodPositions.Add(position); nodes [k].position = position; nodes [k].lifeTime = lifeTime; nodes [k].waterVoxel = waterVoxel; nodes [k].prev = last; nodes [k].next = -1; if (last != -1) { nodes [last].next = k; } last = k; if (root == -1) { root = k; } SetNextSpreadTime(k); return; } } }
IEnumerator ComputePrefabBoxColliderBounds(VoxelDefinition vd) { bool oldActiveState = vd.prefab.activeSelf; vd.prefab.SetActive(false); GameObject dummy = Instantiate <GameObject> (vd.prefab); // Disable all components to avoid undesired effects Component [] components = dummy.GetComponents <Component> (); for (int k = 0; k < components.Length; k++) { MonoBehaviour mono = components [k] as MonoBehaviour; if (mono != null) { mono.enabled = false; } } vd.prefab.SetActive(oldActiveState); dummy.hideFlags = HideFlags.HideAndDontSave; dummy.SetActive(true); dummy.transform.position = new Vector3(0, 10000, 10000); dummy.transform.rotation = Misc.quaternionZero; dummy.transform.localScale = Misc.vector3one; yield return(new WaitForEndOfFrame()); BoxCollider collider = dummy.GetComponentInChildren <BoxCollider> (); Bounds bounds = collider.bounds; bounds.center -= dummy.transform.position; vd.prefabColliderBounds = bounds; Destroy(dummy); }
//int texSize=1024; void DisposeTextures() { if (voxelDefinitions != null) { for (int k = 0; k < voxelDefinitionsCount; k++) { VoxelDefinition vd = voxelDefinitions [k]; if (vd != null) { if (vd.textureThumbnailBottom != null) { DestroyImmediate(vd.textureThumbnailBottom); } if (vd.textureThumbnailSide != null) { DestroyImmediate(vd.textureThumbnailSide); } if (vd.textureThumbnailTop != null) { DestroyImmediate(vd.textureThumbnailTop); } } } } if (modelHighlightMat != null) { DestroyImmediate(modelHighlightMat); } }
/// <summary> /// Inserts an user voxel definition to the array. It doesn't do any safety check nor modifies the voxel definition except assigning an index /// </summary> bool InsertUserVoxelDefinition(VoxelDefinition vd) { if (vd == null) { return(false); } if (vd.index > 0 && vd.index < voxelDefinitionsCount && voxelDefinitions [vd.index] == vd) { return(false); // already added } // Resize voxel definitions array? if (voxelDefinitionsCount >= voxelDefinitions.Length) { voxelDefinitions = voxelDefinitions.Extend(); } // Make space for (int k = voxelDefinitionsCount - 1; k > sessionUserVoxelsLastIndex + 1; k--) { voxelDefinitions [k] = voxelDefinitions [k - 1]; voxelDefinitions [k].index++; } sessionUserVoxelsLastIndex++; vd.index = (ushort)sessionUserVoxelsLastIndex; voxelDefinitions [sessionUserVoxelsLastIndex] = vd; voxelDefinitionsCount++; voxelDefinitionsDict [vd.name] = vd; sessionUserVoxels.Add(vd); return(true); }
/// <summary> /// Used to initialize any data structure or reload /// </summary> protected override void Init() { if (terrainVoxel == null) { terrainVoxel = VoxelPlayEnvironment.instance.defaultVoxel; } }
/// <summary> /// Clears temporary/session non-serializable fields /// </summary> public void Reset() { index = 0; dynamicDefinition = null; dynamicMeshes = null; batchedIndex = -1; hasContent = renderType.hasContent(); }
private void OnValidate() { if (neighbourDefinition == null) { neighbourDefinition = voxelDefinition; } ComputeMatchesMatrix(); }
void GetVoxelThumbnails(VoxelDefinition vd) { Texture2D top, side, bottom; top = side = bottom = null; if (vd.overrideMaterial && vd.texturesByMaterial) { Material mat = vd.overrideMaterialNonGeo; Texture2D tex = (Texture2D)mat.mainTexture; if (tex != null) { #if UNITY_EDITOR string path = UnityEditor.AssetDatabase.GetAssetPath(tex); if (!string.IsNullOrEmpty(path)) { UnityEditor.TextureImporter timp = UnityEditor.AssetImporter.GetAtPath(path) as UnityEditor.TextureImporter; if (timp != null && !timp.isReadable) { timp.isReadable = true; timp.SaveAndReimport(); } } #endif top = side = bottom = Instantiate <Texture2D> (tex); } } else { if (vd.renderType == RenderType.Custom && vd.textureSample != null) { top = side = bottom = vd.textureSample; } else { top = vd.textureTop; side = vd.textureSide; bottom = vd.textureBottom; } } if (top != null) { vd.textureThumbnailTop = Instantiate(top) as Texture2D; vd.textureThumbnailTop.hideFlags = HideFlags.DontSave; TextureTools.Scale(vd.textureThumbnailTop, 64, 64, FilterMode.Point); } if (side != null) { vd.textureThumbnailSide = Instantiate(side) as Texture2D; vd.textureThumbnailSide.hideFlags = HideFlags.DontSave; TextureTools.Scale(vd.textureThumbnailSide, 64, 64, FilterMode.Point); } if (bottom != null) { vd.textureThumbnailBottom = Instantiate(bottom) as Texture2D; vd.textureThumbnailBottom.hideFlags = HideFlags.DontSave; TextureTools.Scale(vd.textureThumbnailBottom, 64, 64, FilterMode.Point); } }
public Voxel(VoxelDefinition type) { if ((object)type != null) { this.hasContent = type.hasContent; switch (type.renderType) { case RenderType.Opaque: case RenderType.Opaque6tex: case RenderType.OpaqueAnimated: this.opaque = 15; this._flags = 0; break; case RenderType.Transp6tex: this.opaque = 2; this._flags = 0; break; case RenderType.Cutout: this.opaque = 3; this._flags = 0; break; case RenderType.Water: this.opaque = 2; this._flags = 0xF; break; case RenderType.OpaqueNoAO: case RenderType.Cloud: this.opaque = 15; this._flags = 0; break; default: this.opaque = type.opaque; this._flags = 0; break; } this.typeIndex = type.index; } else { this.hasContent = (byte)0; this.opaque = (byte)0; this.typeIndex = 0; this._flags = 0; } this.light = 0; this.torchLight = 0; this.lightMesh = 0; #if USES_TINTING this.red = this.green = this.blue = 255; #endif }
void SpawnOre(VoxelChunk chunk, VoxelDefinition oreDefinition, Vector3 veinPos, int px, int py, int pz, int veinSize, int minDepth, int maxDepth) { int voxelIndex = py * ONE_Y_ROW + pz * ONE_Z_ROW + px; while (veinSize-- > 0 && voxelIndex >= 0 && voxelIndex < chunk.voxels.Length) { // Get height at position int groundLevel = heightChunkData [pz * 16 + px].groundLevel; int depth = (int)(groundLevel - veinPos.y); if (depth < minDepth || depth > maxDepth) { return; } // Replace solid voxels with ore if (chunk.voxels [voxelIndex].opaque >= 15) { chunk.voxels [voxelIndex].SetFastOpaque(oreDefinition); } // Check if spawn continues Vector3 prevPos = veinPos; float v = WorldRand.GetValue(veinPos); int dir = (int)(v * 5); switch (dir) { case 0: // down veinPos.y--; voxelIndex -= ONE_Y_ROW; break; case 1: // right veinPos.x++; voxelIndex++; break; case 2: // back veinPos.z--; voxelIndex -= ONE_Z_ROW; break; case 3: // left veinPos.x--; voxelIndex--; break; case 4: // forward veinPos.z++; voxelIndex += ONE_Z_ROW; break; } if (veinPos.x == prevPos.x && veinPos.y == prevPos.y && veinPos.z == prevPos.z) { veinPos.y--; voxelIndex -= ONE_Y_ROW; } } }
/// <summary> /// Creates the vegetation. /// </summary> void CreateVegetation (VoxelChunk chunk, int voxelIndex, VoxelDefinition vd) { if (chunk != null) { // Updates current chunk if (chunk.allowTrees && chunk.voxels [voxelIndex].hasContent != 1) { chunk.voxels [voxelIndex].Set (vd); vegetationCreated++; } } }
void SaveGameFormat_1_2(TextWriter sw) { // Build a table with all voxel definitions used in modified chunks InitSaveGameStructs(); VoxelDefinition last = null; int count = 0; foreach (KeyValuePair <int, CachedChunk> kv in cachedChunks) { VoxelChunk chunk = kv.Value.chunk; if (chunk != null && chunk.modified) { for (int k = 0; k < chunk.voxels.Length; k++) { VoxelDefinition vd = chunk.voxels[k].type; if (vd == null || vd == last) { continue; } last = vd; if (!saveVoxelDefinitionsDict.ContainsKey(vd)) { saveVoxelDefinitionsDict[vd] = count++; saveVoxelDefinitionsList.Add(vd.name); } } } } // Header sw.WriteLine(SAVE_FILE_CURRENT_FORMAT); // Character controller transform position sw.WriteLine(characterController.transform.position.x + "," + characterController.transform.position.y + "," + characterController.transform.position.z); // Character controller transform rotation sw.WriteLine(characterController.transform.rotation.eulerAngles.x + "," + characterController.transform.rotation.eulerAngles.y + "," + characterController.transform.rotation.eulerAngles.z); // Character controller's camera local rotation sw.WriteLine(cameraMain.transform.localRotation.eulerAngles.x + "," + cameraMain.transform.localRotation.eulerAngles.y + "," + cameraMain.transform.localRotation.eulerAngles.z); // Add voxel definitions table int vdCount = saveVoxelDefinitionsList.Count; sw.WriteLine(vdCount); for (int k = 0; k < vdCount; k++) { sw.WriteLine(saveVoxelDefinitionsList[k]); } // Add modified chunks foreach (KeyValuePair <int, CachedChunk> kv in cachedChunks) { VoxelChunk chunk = kv.Value.chunk; if (chunk != null && chunk.modified) { WriteChunkData1_2(sw, chunk); } } }
public void SetFast(VoxelDefinition type, byte opaque, Color32 tintColor) { #if USES_TINTING this.red = tintColor.r; this.green = tintColor.g; this.blue = tintColor.b; #endif this.typeIndex = type.index; this.hasContent = type.hasContent; this.opaque = type.opaque; this._flags = 0; }
public void SetFastOpaque(VoxelDefinition type) { #if USES_TINTING this.red = type.tintColor.r; this.green = type.tintColor.g; this.blue = type.tintColor.b; #endif this.typeIndex = type.index; //this.type = type; this.hasContent = type.hasContent; this.opaque = 15; this._flags = 0; }
public static ModelDefinition GetModelDefinition(VoxelDefinition voxelTemplate, ColorBasedModelDefinition model, bool ignoreOffset, ColorToVoxelMap colorMap = null) { ModelDefinition md = ScriptableObject.CreateInstance <ModelDefinition> (); md.sizeX = model.sizeX; md.sizeY = model.sizeY; md.sizeZ = model.sizeZ; if (!ignoreOffset) { md.offsetX = model.offsetX; md.offsetY = model.offsetY; md.offsetZ = model.offsetZ; } if (colorMap != null && voxelTemplate == null) { voxelTemplate = colorMap.defaultVoxelDefinition; } List <ModelBit> bits = new List <ModelBit> (); for (int y = 0; y < model.sizeY; y++) { int posy = y * model.sizeX * model.sizeZ; for (int z = 0; z < model.sizeZ; z++) { int posz = z * model.sizeX; for (int x = 0; x < model.sizeX; x++) { int index = posy + posz + x; if (model.colors [index].a > 0) { ModelBit bit = new ModelBit(); bit.voxelIndex = index; if (colorMap != null) { bit.voxelDefinition = colorMap.GetVoxelDefinition(model.colors [index], voxelTemplate); bit.color = Misc.color32White; //bit.color = colorMap.colorMap[index].color; } else { bit.voxelDefinition = voxelTemplate; bit.color = model.colors [index]; } bits.Add(bit); } } } } md.bits = bits.ToArray(); return(md); }
protected void CheckDamage(VoxelDefinition voxelType) { if (voxelType == null) { return; } int playerDamage = voxelType.playerDamage; if (playerDamage > 0 && Time.time > nextPlayerDamageTime) { nextPlayerDamageTime = Time.time + voxelType.playerDamageDelay; player.DamageToPlayer(playerDamage); } }
/// <summary> /// Internal method that puts a voxel in a given position. This method does not inform to neighbours. Only used by non-contiguous structures, like trees or vegetation. /// For terrain or large scale buildings, use VoxelPlaceFast. /// </summary> /// <param name="position">Position.</param> /// <param name="voxelType">Voxel type.</param> /// <param name="chunk">Chunk.</param> /// <param name="voxelIndex">Voxel index.</param> void VoxelSingleSet(Vector3 position, VoxelDefinition voxelType, out VoxelChunk chunk, out int voxelIndex, Color32 tintColor) { if (GetVoxelIndex(position, out chunk, out voxelIndex)) { if (OnVoxelBeforePlace != null) { OnVoxelBeforePlace(position, chunk, voxelIndex, ref voxelType, ref tintColor); if (voxelType == null) { return; } } chunk.voxels [voxelIndex].Set(voxelType, tintColor); } }
/// <summary> /// Requests the vegetation creation. /// </summary> public void RequestVegetationCreation (VoxelChunk chunk, int voxelIndex, VoxelDefinition vd) { if (chunk == null || !enableVegetation) { return; } vegetationRequestLast++; if (vegetationRequestLast >= vegetationRequests.Length) { vegetationRequestLast = 0; } if (vegetationRequestLast != vegetationRequestFirst) { vegetationRequests [vegetationRequestLast].chunk = chunk; vegetationRequests [vegetationRequestLast].chunkOriginalPosition = chunk.position; vegetationRequests [vegetationRequestLast].voxelIndex = voxelIndex; vegetationRequests [vegetationRequestLast].vd = vd; vegetationInCreationQueueCount++; } }
public void Set(VoxelDefinition type, Color32 tintColor) { #if USES_TINTING this.red = tintColor.r; this.green = tintColor.g; this.blue = tintColor.b; #endif this.typeIndex = type.index; this.hasContent = type.hasContent; switch (type.renderType) { case RenderType.Opaque: case RenderType.Opaque6tex: case RenderType.OpaqueAnimated: this.opaque = 15; this._flags = 0; break; case RenderType.Transp6tex: this.opaque = 2; this._flags = 0; break; case RenderType.Cutout: this.opaque = 3; this._flags = 0; break; case RenderType.Water: this.opaque = 2; this._flags = type.height; break; case RenderType.OpaqueNoAO: case RenderType.Cloud: this.opaque = 15; this._flags = 0; break; default: this.opaque = type.opaque; this._flags = 0; break; } }
void SetParticleMaterialTextures(Material mat, VoxelDefinition voxelType, Color32 color) { if (voxelType.renderType == RenderType.CutoutCross) { // vegetation only uses sample colors mat.mainTexture = Texture2D.whiteTexture; mat.SetTexture("_TexSides", Texture2D.whiteTexture); mat.SetTexture("_TexBottom", Texture2D.whiteTexture); float r = 0.8f + Random.value * 0.4f; // color variation Color vegetationColor = new Color(voxelType.sampleColor.r * r, voxelType.sampleColor.g * r, voxelType.sampleColor.b * r, 1f); mat.SetColor("_Color", vegetationColor); } else { mat.mainTexture = voxelType.textureThumbnailTop; mat.SetTexture("_TexSides", voxelType.textureThumbnailSide); mat.SetTexture("_TexBottom", voxelType.textureThumbnailBottom); mat.SetColor("_Color", color); } }
VoxelDefinition GenerateDoorVoxel() { GameObject doorPrefab = GenerateCubePrefab(); if (doorPrefab == null) { return(null); } Door door = doorPrefab.AddComponent <Door> (); if (offsetOption == CubeVertexOffsetOption.DoorOpensToTheLeft) { door.customTag = "left"; } VoxelDefinition doorVoxel = ScriptableObject.CreateInstance <VoxelDefinition> (); doorVoxel.renderType = RenderType.Custom; doorVoxel.model = doorPrefab; doorVoxel.name = "Voxel" + doorPrefab.name; doorVoxel.icon = icon; if (offsetOption == CubeVertexOffsetOption.DoorOpensToTheRight) { doorVoxel.offset = new Vector3(scale.x * 0.5f - scale.z * 0.5f, -0.5f, 0); } else { doorVoxel.offset = new Vector3(scale.x * -0.5f + scale.z * 0.5f, -0.5f, 0); } string path = AssetDatabase.GetAssetPath(doorPrefab); if (path != null) { path = System.IO.Path.GetDirectoryName(path); } AssetDatabase.CreateAsset(doorVoxel, path + "/" + doorPrefab.name + ".asset"); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); return(doorVoxel); }
/// <summary> /// Clears temporary/session non-serializable fields /// </summary> public void Reset() { index = 0; dynamicDefinition = null; dynamicMeshes = null; batchedIndex = -1; doNotSave = false; textureIndexBottom = textureIndexSide = textureIndexTop = 0; isDynamic = false; dynamicDefinition = null; staticDefinition = null; seeThroughVoxelTempTransp = 0; materialBufferIndex = 0; if (dynamicMeshes != null) { dynamicMeshes.Clear(); } _mesh = null; _material = null; hasContent = renderType.hasContent(); }
/// <summary> /// Selects an item from inventory by its voxel definition type /// </summary> public bool SetSelectedItem(VoxelDefinition vd) { if (this.items == null) { return(false); } List <InventoryItem> items = this.items; int count = items.Count; for (int k = 0; k < count; k++) { InventoryItem item = items [k]; if (item.item.category == ItemCategory.Voxel && item.item.voxelType == vd) { selectedItemIndex = k; return(true); } } return(false); }
void GetVoxelThumbnails(VoxelDefinition vd) { if (vd.textureTop != null) { vd.textureThumbnailTop = Instantiate(vd.textureTop) as Texture2D; vd.textureThumbnailTop.hideFlags = HideFlags.DontSave; TextureTools.Scale(vd.textureThumbnailTop, 64, 64, FilterMode.Point); } if (vd.textureSide != null) { vd.textureThumbnailSide = Instantiate(vd.textureSide) as Texture2D; vd.textureThumbnailSide.hideFlags = HideFlags.DontSave; TextureTools.Scale(vd.textureThumbnailSide, 64, 64, FilterMode.Point); } if (vd.textureBottom != null) { vd.textureThumbnailBottom = Instantiate(vd.textureBottom) as Texture2D; vd.textureThumbnailBottom.hideFlags = HideFlags.DontSave; TextureTools.Scale(vd.textureThumbnailBottom, 64, 64, FilterMode.Point); } }