[Fix] Update Voxel World
This commit is contained in:
@@ -414,10 +414,10 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier: VoxelWorld.Runtime::InfiniteWorld.VoxelWorld.VoxelWorldGenerator
|
||||
streamTarget: {fileID: 1331065949}
|
||||
chunkSize: 16
|
||||
generationRadius: 4
|
||||
generationRadius: 7
|
||||
blockingGenerationRadius: 1
|
||||
seed: 12345
|
||||
maxMountainHeight: 5
|
||||
maxMountainHeight: 12
|
||||
macroNoiseScale: 0.05
|
||||
detailNoiseScale: 0.12
|
||||
ridgeNoiseScale: 0.18
|
||||
@@ -436,7 +436,7 @@ MonoBehaviour:
|
||||
- {fileID: 11400000, guid: eac8d825dd62e1c439235d273a4ca613, type: 2}
|
||||
- {fileID: 11400000, guid: 6d0dbe510ed048440a3925ef40aeeb5b, type: 2}
|
||||
biomeNoiseScale: 0.02
|
||||
biomeSize: 12
|
||||
biomeSize: 4
|
||||
maxAsyncChunkJobs: 8
|
||||
maxChunkBuildsPerFrame: 2
|
||||
--- !u!4 &1842209028
|
||||
|
||||
@@ -41,6 +41,8 @@ namespace InfiniteWorld.VoxelWorld
|
||||
internal sealed class VoxelWorldAtlas : IDisposable
|
||||
{
|
||||
private const int FallbackTileSize = 32;
|
||||
private const string ShaderName = "Infinite World/VoxelWorld/TextureArrayUnlit";
|
||||
|
||||
private static readonly VoxelSurfaceType[] SurfaceOrder =
|
||||
{
|
||||
VoxelSurfaceType.CliffTop,
|
||||
@@ -49,29 +51,29 @@ namespace InfiniteWorld.VoxelWorld
|
||||
VoxelSurfaceType.WalkableSurface
|
||||
};
|
||||
|
||||
private readonly Dictionary<int, Dictionary<VoxelSurfaceType, Rect>> uvLookup;
|
||||
private readonly Dictionary<int, Dictionary<VoxelSurfaceType, int>> layerLookup;
|
||||
|
||||
public VoxelWorldAtlas(Texture2D texture, Material material, Dictionary<int, Dictionary<VoxelSurfaceType, Rect>> uvRects)
|
||||
private VoxelWorldAtlas(Texture2DArray textureArray, Material material, Dictionary<int, Dictionary<VoxelSurfaceType, int>> layers)
|
||||
{
|
||||
Texture = texture;
|
||||
TextureArray = textureArray;
|
||||
Material = material;
|
||||
uvLookup = uvRects;
|
||||
layerLookup = layers;
|
||||
}
|
||||
|
||||
public Texture2D Texture { get; }
|
||||
public Texture2DArray TextureArray { get; }
|
||||
|
||||
public Material Material { get; }
|
||||
|
||||
public int BiomeCount => uvLookup.Count;
|
||||
public int BiomeCount => layerLookup.Count;
|
||||
|
||||
public Rect GetUvRect(int biomeIndex, VoxelSurfaceType surfaceType)
|
||||
public int GetTextureLayer(int biomeIndex, VoxelSurfaceType surfaceType)
|
||||
{
|
||||
if (uvLookup.TryGetValue(biomeIndex, out Dictionary<VoxelSurfaceType, Rect> biomeRects) && biomeRects.TryGetValue(surfaceType, out Rect rect))
|
||||
if (layerLookup.TryGetValue(biomeIndex, out Dictionary<VoxelSurfaceType, int> biomeLayers) && biomeLayers.TryGetValue(surfaceType, out int layer))
|
||||
{
|
||||
return rect;
|
||||
return layer;
|
||||
}
|
||||
|
||||
return uvLookup[0][surfaceType];
|
||||
return layerLookup[0][surfaceType];
|
||||
}
|
||||
|
||||
public static VoxelWorldAtlas CreateRuntimeAtlas(IReadOnlyList<VoxelBiomeProfile> biomeProfiles)
|
||||
@@ -94,48 +96,60 @@ namespace InfiniteWorld.VoxelWorld
|
||||
}
|
||||
|
||||
int tileSize = DetermineTileSize(sourceBiomes);
|
||||
Texture2D texture = new Texture2D(tileSize * SurfaceOrder.Length, tileSize * sourceBiomes.Count, TextureFormat.RGBA32, false)
|
||||
int layerCount = sourceBiomes.Count * SurfaceOrder.Length;
|
||||
Texture2DArray textureArray = new Texture2DArray(tileSize, tileSize, layerCount, TextureFormat.RGBA32, false, false)
|
||||
{
|
||||
filterMode = FilterMode.Point,
|
||||
wrapMode = TextureWrapMode.Clamp,
|
||||
name = "VoxelWorld_RuntimeAtlas",
|
||||
wrapMode = TextureWrapMode.Repeat,
|
||||
anisoLevel = 1,
|
||||
name = "VoxelWorld_RuntimeTextureArray",
|
||||
hideFlags = HideFlags.HideAndDontSave
|
||||
};
|
||||
|
||||
Fill(texture, new Color(1f, 0f, 1f, 1f));
|
||||
|
||||
Dictionary<int, Dictionary<VoxelSurfaceType, Rect>> uvRects = new Dictionary<int, Dictionary<VoxelSurfaceType, Rect>>(sourceBiomes.Count);
|
||||
Dictionary<int, Dictionary<VoxelSurfaceType, int>> layers = new Dictionary<int, Dictionary<VoxelSurfaceType, int>>(sourceBiomes.Count);
|
||||
int layerIndex = 0;
|
||||
for (int biomeIndex = 0; biomeIndex < sourceBiomes.Count; biomeIndex++)
|
||||
{
|
||||
Dictionary<VoxelSurfaceType, Rect> biomeRects = new Dictionary<VoxelSurfaceType, Rect>(SurfaceOrder.Length);
|
||||
uvRects[biomeIndex] = biomeRects;
|
||||
Dictionary<VoxelSurfaceType, int> biomeLayers = new Dictionary<VoxelSurfaceType, int>(SurfaceOrder.Length);
|
||||
layers[biomeIndex] = biomeLayers;
|
||||
|
||||
for (int surfaceIndex = 0; surfaceIndex < SurfaceOrder.Length; surfaceIndex++)
|
||||
{
|
||||
VoxelSurfaceType surfaceType = SurfaceOrder[surfaceIndex];
|
||||
RectInt rect = new RectInt(surfaceIndex * tileSize, biomeIndex * tileSize, tileSize, tileSize);
|
||||
DrawSurfaceTile(texture, rect, sourceBiomes[biomeIndex], surfaceType, tileSize);
|
||||
biomeRects[surfaceType] = BuildUvRect(rect, texture.width, texture.height);
|
||||
Texture2D tileTexture = BuildSurfaceTexture(sourceBiomes[biomeIndex], surfaceType, tileSize);
|
||||
CopyTextureToArrayLayer(tileTexture, textureArray, layerIndex);
|
||||
biomeLayers[surfaceType] = layerIndex;
|
||||
layerIndex++;
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
UnityEngine.Object.Destroy(tileTexture);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(tileTexture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
texture.Apply(false, false);
|
||||
textureArray.Apply(false, false);
|
||||
|
||||
Shader shader = Shader.Find(ShaderName);
|
||||
if (shader == null)
|
||||
{
|
||||
shader = Shader.Find("Universal Render Pipeline/Unlit") ?? Shader.Find("Standard");
|
||||
Debug.LogError($"Shader '{ShaderName}' was not found. Falling back to '{shader?.name ?? "<missing>"}', but voxel texture array sampling will not work until the shader asset is imported correctly.");
|
||||
}
|
||||
|
||||
Shader shader = Shader.Find("Universal Render Pipeline/Unlit") ?? Shader.Find("Universal Render Pipeline/Lit") ?? Shader.Find("Standard");
|
||||
Material material = new Material(shader)
|
||||
{
|
||||
name = "VoxelWorld_RuntimeMaterial",
|
||||
hideFlags = HideFlags.HideAndDontSave
|
||||
};
|
||||
|
||||
if (material.HasProperty("_BaseMap"))
|
||||
if (material.HasProperty("_TextureArray"))
|
||||
{
|
||||
material.SetTexture("_BaseMap", texture);
|
||||
}
|
||||
|
||||
if (material.HasProperty("_MainTex"))
|
||||
{
|
||||
material.SetTexture("_MainTex", texture);
|
||||
material.SetTexture("_TextureArray", textureArray);
|
||||
}
|
||||
|
||||
if (material.HasProperty("_BaseColor"))
|
||||
@@ -148,19 +162,19 @@ namespace InfiniteWorld.VoxelWorld
|
||||
material.SetColor("_Color", Color.white);
|
||||
}
|
||||
|
||||
return new VoxelWorldAtlas(texture, material, uvRects);
|
||||
return new VoxelWorldAtlas(textureArray, material, layers);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
UnityEngine.Object.Destroy(Texture);
|
||||
UnityEngine.Object.Destroy(TextureArray);
|
||||
UnityEngine.Object.Destroy(Material);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(Texture);
|
||||
UnityEngine.Object.DestroyImmediate(TextureArray);
|
||||
UnityEngine.Object.DestroyImmediate(Material);
|
||||
}
|
||||
}
|
||||
@@ -187,45 +201,66 @@ namespace InfiniteWorld.VoxelWorld
|
||||
return Mathf.Max(FallbackTileSize, tileSize);
|
||||
}
|
||||
|
||||
private static Rect BuildUvRect(RectInt rect, int textureWidth, int textureHeight)
|
||||
private static Texture2D BuildSurfaceTexture(VoxelBiomeProfile biome, VoxelSurfaceType surfaceType, int tileSize)
|
||||
{
|
||||
float paddingX = 0.5f / textureWidth;
|
||||
float paddingY = 0.5f / textureHeight;
|
||||
return new Rect(
|
||||
rect.xMin / (float)textureWidth + paddingX,
|
||||
rect.yMin / (float)textureHeight + paddingY,
|
||||
rect.width / (float)textureWidth - paddingX * 2f,
|
||||
rect.height / (float)textureHeight - paddingY * 2f);
|
||||
Texture2D texture = new Texture2D(tileSize, tileSize, TextureFormat.RGBA32, false)
|
||||
{
|
||||
filterMode = FilterMode.Point,
|
||||
wrapMode = TextureWrapMode.Repeat,
|
||||
hideFlags = HideFlags.HideAndDontSave
|
||||
};
|
||||
|
||||
Fill(texture, new Color(1f, 0f, 1f, 1f));
|
||||
Sprite sprite = biome != null ? biome.GetSprite(surfaceType) : null;
|
||||
if (sprite == null || !TryBlitSprite(texture, sprite))
|
||||
{
|
||||
DrawFallbackSurface(texture, new RectInt(0, 0, tileSize, tileSize), surfaceType);
|
||||
}
|
||||
|
||||
private static void DrawSurfaceTile(Texture2D atlas, RectInt targetRect, VoxelBiomeProfile biome, VoxelSurfaceType surfaceType, int tileSize)
|
||||
texture.Apply(false, false);
|
||||
return texture;
|
||||
}
|
||||
|
||||
private static void CopyTextureToArrayLayer(Texture2D source, Texture2DArray destination, int layerIndex)
|
||||
{
|
||||
Sprite sprite = biome != null ? biome.GetSprite(surfaceType) : null;
|
||||
if (sprite != null && TryBlitSprite(atlas, targetRect, sprite))
|
||||
bool canUseCopyTexture = SystemInfo.supports2DArrayTextures &&
|
||||
SystemInfo.copyTextureSupport != UnityEngine.Rendering.CopyTextureSupport.None &&
|
||||
source.width == destination.width &&
|
||||
source.height == destination.height;
|
||||
|
||||
if (canUseCopyTexture)
|
||||
{
|
||||
try
|
||||
{
|
||||
Graphics.CopyTexture(source, 0, 0, destination, layerIndex, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
DrawFallbackSurface(atlas, targetRect, surfaceType);
|
||||
catch (Exception)
|
||||
{
|
||||
// Fall back to CPU copy when runtime copy is unavailable for this platform/import setup.
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryBlitSprite(Texture2D atlas, RectInt targetRect, Sprite sprite)
|
||||
destination.SetPixels(source.GetPixels(), layerIndex, 0);
|
||||
}
|
||||
|
||||
private static bool TryBlitSprite(Texture2D target, Sprite sprite)
|
||||
{
|
||||
try
|
||||
{
|
||||
Texture2D source = sprite.texture;
|
||||
Rect spriteRect = sprite.textureRect;
|
||||
|
||||
for (int y = 0; y < targetRect.height; y++)
|
||||
for (int y = 0; y < target.height; y++)
|
||||
{
|
||||
float sampleY = Mathf.Lerp(spriteRect.yMin, spriteRect.yMax - 1f, y / (float)Mathf.Max(1, targetRect.height - 1));
|
||||
float sampleY = Mathf.Lerp(spriteRect.yMin, spriteRect.yMax - 1f, y / (float)Mathf.Max(1, target.height - 1));
|
||||
int sourceY = Mathf.Clamp(Mathf.RoundToInt(sampleY), 0, source.height - 1);
|
||||
|
||||
for (int x = 0; x < targetRect.width; x++)
|
||||
for (int x = 0; x < target.width; x++)
|
||||
{
|
||||
float sampleX = Mathf.Lerp(spriteRect.xMin, spriteRect.xMax - 1f, x / (float)Mathf.Max(1, targetRect.width - 1));
|
||||
float sampleX = Mathf.Lerp(spriteRect.xMin, spriteRect.xMax - 1f, x / (float)Mathf.Max(1, target.width - 1));
|
||||
int sourceX = Mathf.Clamp(Mathf.RoundToInt(sampleX), 0, source.width - 1);
|
||||
atlas.SetPixel(targetRect.x + x, targetRect.y + y, source.GetPixel(sourceX, sourceY));
|
||||
target.SetPixel(x, y, source.GetPixel(sourceX, sourceY));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +268,7 @@ namespace InfiniteWorld.VoxelWorld
|
||||
}
|
||||
catch (UnityException)
|
||||
{
|
||||
Debug.LogWarning($"Sprite '{sprite.name}' texture is not readable. Falling back to generated tile for voxel biome atlas.");
|
||||
Debug.LogWarning($"Sprite '{sprite.name}' texture is not readable. Falling back to generated tile for voxel biome texture array.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,38 +400,136 @@ namespace InfiniteWorld.VoxelWorld
|
||||
|
||||
private void BuildGroundSurface(int[] heights, byte[] biomeIndices, MeshBuffers renderBuffers)
|
||||
{
|
||||
bool[,] visited = new bool[chunkSize, chunkSize];
|
||||
for (int z = 0; z < chunkSize; z++)
|
||||
{
|
||||
for (int x = 0; x < chunkSize; x++)
|
||||
{
|
||||
if (heights[z * chunkSize + x] > 0)
|
||||
int index = z * chunkSize + x;
|
||||
if (visited[x, z] || heights[index] > 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int index = z * chunkSize + x;
|
||||
AddTopQuad(renderBuffers, x, z, 1f, 1f, 0f, atlas.GetUvRect(biomeIndices[index], VoxelSurfaceType.WalkableSurface));
|
||||
byte biomeIndex = biomeIndices[index];
|
||||
int width = 1;
|
||||
while (x + width < chunkSize && !visited[x + width, z] && heights[z * chunkSize + x + width] == 0 && biomeIndices[z * chunkSize + x + width] == biomeIndex)
|
||||
{
|
||||
width++;
|
||||
}
|
||||
|
||||
int depth = 1;
|
||||
bool canGrow = true;
|
||||
while (z + depth < chunkSize && canGrow)
|
||||
{
|
||||
for (int ix = 0; ix < width; ix++)
|
||||
{
|
||||
int candidateIndex = (z + depth) * chunkSize + x + ix;
|
||||
if (visited[x + ix, z + depth] || heights[candidateIndex] > 0 || biomeIndices[candidateIndex] != biomeIndex)
|
||||
{
|
||||
canGrow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (canGrow)
|
||||
{
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int dz = 0; dz < depth; dz++)
|
||||
{
|
||||
for (int dx = 0; dx < width; dx++)
|
||||
{
|
||||
visited[x + dx, z + dz] = true;
|
||||
}
|
||||
}
|
||||
|
||||
AddTopQuad(renderBuffers, x, z, width, depth, 0f, atlas.GetTextureLayer(biomeIndex, VoxelSurfaceType.WalkableSurface));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildMountainTops(Vector2Int coord, int[] heights, byte[] biomeIndices, MeshBuffers renderBuffers, MeshBuffers colliderBuffers)
|
||||
{
|
||||
bool[,] visited = new bool[chunkSize, chunkSize];
|
||||
for (int z = 0; z < chunkSize; z++)
|
||||
{
|
||||
for (int x = 0; x < chunkSize; x++)
|
||||
{
|
||||
int height = heights[z * chunkSize + x];
|
||||
if (height <= 0)
|
||||
if (height <= 0 || visited[x, z])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int index = z * chunkSize + x;
|
||||
VoxelSurfaceType surfaceType = IsCliffTop(ChunkToWorldCell(coord, x, z), height) ? VoxelSurfaceType.CliffTop : VoxelSurfaceType.WalkableSurface;
|
||||
Rect uvRect = atlas.GetUvRect(biomeIndices[index], surfaceType);
|
||||
AddTopQuad(renderBuffers, x, z, 1f, 1f, height, uvRect);
|
||||
AddTopQuad(colliderBuffers, x, z, 1f, 1f, height, Rect.zero, false);
|
||||
byte biomeIndex = biomeIndices[index];
|
||||
|
||||
int width = 1;
|
||||
while (x + width < chunkSize && !visited[x + width, z])
|
||||
{
|
||||
int candidateIndex = z * chunkSize + x + width;
|
||||
if (heights[candidateIndex] != height || biomeIndices[candidateIndex] != biomeIndex)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
VoxelSurfaceType candidateSurface = IsCliffTop(ChunkToWorldCell(coord, x + width, z), height) ? VoxelSurfaceType.CliffTop : VoxelSurfaceType.WalkableSurface;
|
||||
if (candidateSurface != surfaceType)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
width++;
|
||||
}
|
||||
|
||||
int depth = 1;
|
||||
bool canGrow = true;
|
||||
while (z + depth < chunkSize && canGrow)
|
||||
{
|
||||
for (int ix = 0; ix < width; ix++)
|
||||
{
|
||||
if (visited[x + ix, z + depth])
|
||||
{
|
||||
canGrow = false;
|
||||
break;
|
||||
}
|
||||
|
||||
int candidateIndex = (z + depth) * chunkSize + x + ix;
|
||||
if (heights[candidateIndex] != height || biomeIndices[candidateIndex] != biomeIndex)
|
||||
{
|
||||
canGrow = false;
|
||||
break;
|
||||
}
|
||||
|
||||
VoxelSurfaceType candidateSurface = IsCliffTop(ChunkToWorldCell(coord, x + ix, z + depth), height) ? VoxelSurfaceType.CliffTop : VoxelSurfaceType.WalkableSurface;
|
||||
if (candidateSurface != surfaceType)
|
||||
{
|
||||
canGrow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (canGrow)
|
||||
{
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int dz = 0; dz < depth; dz++)
|
||||
{
|
||||
for (int dx = 0; dx < width; dx++)
|
||||
{
|
||||
visited[x + dx, z + dz] = true;
|
||||
}
|
||||
}
|
||||
|
||||
int textureLayer = atlas.GetTextureLayer(biomeIndex, surfaceType);
|
||||
AddTopQuad(renderBuffers, x, z, width, depth, height, textureLayer);
|
||||
AddTopQuad(colliderBuffers, x, z, width, depth, height, 0, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -440,104 +538,210 @@ namespace InfiniteWorld.VoxelWorld
|
||||
{
|
||||
for (int z = 0; z < chunkSize; z++)
|
||||
{
|
||||
BuildNorthSouthFaceSlice(coord, heights, biomeIndices, renderBuffers, colliderBuffers, z, true);
|
||||
BuildNorthSouthFaceSlice(coord, heights, biomeIndices, renderBuffers, colliderBuffers, z, false);
|
||||
}
|
||||
|
||||
for (int x = 0; x < chunkSize; x++)
|
||||
{
|
||||
int current = heights[z * chunkSize + x];
|
||||
if (current <= 0)
|
||||
BuildEastWestFaceSlice(coord, heights, biomeIndices, renderBuffers, colliderBuffers, x, true);
|
||||
BuildEastWestFaceSlice(coord, heights, biomeIndices, renderBuffers, colliderBuffers, x, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildNorthSouthFaceSlice(Vector2Int coord, int[] heights, byte[] biomeIndices, MeshBuffers renderBuffers, MeshBuffers colliderBuffers, int z, bool north)
|
||||
{
|
||||
bool[,] visited = new bool[chunkSize, maxMountainHeight];
|
||||
for (int x = 0; x < chunkSize; x++)
|
||||
{
|
||||
for (int y = 0; y < maxMountainHeight; y++)
|
||||
{
|
||||
if (visited[x, y] || !TryGetNorthSouthFace(coord, heights, biomeIndices, x, z, y, north, out byte biomeIndex, out VoxelSurfaceType surfaceType, out int textureLayer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int worldX = coord.x * chunkSize + x;
|
||||
int worldZ = coord.y * chunkSize + z;
|
||||
byte biomeIndex = biomeIndices[z * chunkSize + x];
|
||||
|
||||
AddNorthSouthFace(renderBuffers, colliderBuffers, x, z, current, GetHeightAtWorldCell(new Vector2Int(worldX, worldZ + 1)), biomeIndex, true);
|
||||
AddNorthSouthFace(renderBuffers, colliderBuffers, x, z, current, GetHeightAtWorldCell(new Vector2Int(worldX, worldZ - 1)), biomeIndex, false);
|
||||
AddEastWestFace(renderBuffers, colliderBuffers, x, z, current, GetHeightAtWorldCell(new Vector2Int(worldX + 1, worldZ)), biomeIndex, true);
|
||||
AddEastWestFace(renderBuffers, colliderBuffers, x, z, current, GetHeightAtWorldCell(new Vector2Int(worldX - 1, worldZ)), biomeIndex, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddNorthSouthFace(MeshBuffers renderBuffers, MeshBuffers colliderBuffers, int x, int z, int current, int neighbor, byte biomeIndex, bool north)
|
||||
int width = 1;
|
||||
while (x + width < chunkSize && !visited[x + width, y] && TryGetNorthSouthFace(coord, heights, biomeIndices, x + width, z, y, north, out byte candidateBiome, out VoxelSurfaceType candidateSurface, out int candidateTexture) && candidateBiome == biomeIndex && candidateSurface == surfaceType && candidateTexture == textureLayer)
|
||||
{
|
||||
if (current <= neighbor)
|
||||
{
|
||||
return;
|
||||
width++;
|
||||
}
|
||||
|
||||
int height = 1;
|
||||
bool canGrow = true;
|
||||
while (y + height < maxMountainHeight && canGrow)
|
||||
{
|
||||
for (int dx = 0; dx < width; dx++)
|
||||
{
|
||||
if (visited[x + dx, y + height] || !TryGetNorthSouthFace(coord, heights, biomeIndices, x + dx, z, y + height, north, out byte candidateBiome, out VoxelSurfaceType candidateSurface, out int candidateTexture) || candidateBiome != biomeIndex || candidateSurface != surfaceType || candidateTexture != textureLayer)
|
||||
{
|
||||
canGrow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (canGrow)
|
||||
{
|
||||
height++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int dy = 0; dy < height; dy++)
|
||||
{
|
||||
for (int dx = 0; dx < width; dx++)
|
||||
{
|
||||
visited[x + dx, y + dy] = true;
|
||||
}
|
||||
}
|
||||
|
||||
int bottom = Mathf.Min(current, neighbor);
|
||||
float faceZ = north ? z + 1f : z;
|
||||
|
||||
for (int y = bottom; y < current; y++)
|
||||
{
|
||||
VoxelSurfaceType surfaceType = y == current - 1 ? VoxelSurfaceType.CliffSide : VoxelSurfaceType.Dirt;
|
||||
Rect uvRect = atlas.GetUvRect(biomeIndex, surfaceType);
|
||||
Vector3 bl = new Vector3(x, y, faceZ);
|
||||
Vector3 br = new Vector3(x + 1f, y, faceZ);
|
||||
Vector3 tr = new Vector3(x + 1f, y + 1f, faceZ);
|
||||
Vector3 tl = new Vector3(x, y + 1f, faceZ);
|
||||
Vector3 br = new Vector3(x + width, y, faceZ);
|
||||
Vector3 tr = new Vector3(x + width, y + height, faceZ);
|
||||
Vector3 tl = new Vector3(x, y + height, faceZ);
|
||||
|
||||
if (north)
|
||||
{
|
||||
AddVerticalQuad(renderBuffers, bl, br, tr, tl, uvRect, 1f, 1f);
|
||||
AddVerticalQuad(colliderBuffers, bl, br, tr, tl, Rect.zero, 1f, 1f, false);
|
||||
AddVerticalQuad(renderBuffers, bl, br, tr, tl, width, height, textureLayer, Vector3.forward);
|
||||
AddVerticalQuad(colliderBuffers, bl, br, tr, tl, width, height, 0, Vector3.forward, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddVerticalQuad(renderBuffers, br, bl, tl, tr, uvRect, 1f, 1f);
|
||||
AddVerticalQuad(colliderBuffers, br, bl, tl, tr, Rect.zero, 1f, 1f, false);
|
||||
AddVerticalQuad(renderBuffers, br, bl, tl, tr, width, height, textureLayer, Vector3.back);
|
||||
AddVerticalQuad(colliderBuffers, br, bl, tl, tr, width, height, 0, Vector3.back, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddEastWestFace(MeshBuffers renderBuffers, MeshBuffers colliderBuffers, int x, int z, int current, int neighbor, byte biomeIndex, bool east)
|
||||
private void BuildEastWestFaceSlice(Vector2Int coord, int[] heights, byte[] biomeIndices, MeshBuffers renderBuffers, MeshBuffers colliderBuffers, int x, bool east)
|
||||
{
|
||||
if (current <= neighbor)
|
||||
bool[,] visited = new bool[chunkSize, maxMountainHeight];
|
||||
for (int z = 0; z < chunkSize; z++)
|
||||
{
|
||||
return;
|
||||
for (int y = 0; y < maxMountainHeight; y++)
|
||||
{
|
||||
if (visited[z, y] || !TryGetEastWestFace(coord, heights, biomeIndices, x, z, y, east, out byte biomeIndex, out VoxelSurfaceType surfaceType, out int textureLayer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int width = 1;
|
||||
while (z + width < chunkSize && !visited[z + width, y] && TryGetEastWestFace(coord, heights, biomeIndices, x, z + width, y, east, out byte candidateBiome, out VoxelSurfaceType candidateSurface, out int candidateTexture) && candidateBiome == biomeIndex && candidateSurface == surfaceType && candidateTexture == textureLayer)
|
||||
{
|
||||
width++;
|
||||
}
|
||||
|
||||
int height = 1;
|
||||
bool canGrow = true;
|
||||
while (y + height < maxMountainHeight && canGrow)
|
||||
{
|
||||
for (int dz = 0; dz < width; dz++)
|
||||
{
|
||||
if (visited[z + dz, y + height] || !TryGetEastWestFace(coord, heights, biomeIndices, x, z + dz, y + height, east, out byte candidateBiome, out VoxelSurfaceType candidateSurface, out int candidateTexture) || candidateBiome != biomeIndex || candidateSurface != surfaceType || candidateTexture != textureLayer)
|
||||
{
|
||||
canGrow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (canGrow)
|
||||
{
|
||||
height++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int dy = 0; dy < height; dy++)
|
||||
{
|
||||
for (int dz = 0; dz < width; dz++)
|
||||
{
|
||||
visited[z + dz, y + dy] = true;
|
||||
}
|
||||
}
|
||||
|
||||
int bottom = Mathf.Min(current, neighbor);
|
||||
float faceX = east ? x + 1f : x;
|
||||
|
||||
for (int y = bottom; y < current; y++)
|
||||
{
|
||||
VoxelSurfaceType surfaceType = y == current - 1 ? VoxelSurfaceType.CliffSide : VoxelSurfaceType.Dirt;
|
||||
Rect uvRect = atlas.GetUvRect(biomeIndex, surfaceType);
|
||||
Vector3 bl = new Vector3(faceX, y, z);
|
||||
Vector3 br = new Vector3(faceX, y, z + 1f);
|
||||
Vector3 tr = new Vector3(faceX, y + 1f, z + 1f);
|
||||
Vector3 tl = new Vector3(faceX, y + 1f, z);
|
||||
Vector3 br = new Vector3(faceX, y, z + width);
|
||||
Vector3 tr = new Vector3(faceX, y + height, z + width);
|
||||
Vector3 tl = new Vector3(faceX, y + height, z);
|
||||
|
||||
if (east)
|
||||
{
|
||||
AddVerticalQuad(renderBuffers, br, bl, tl, tr, uvRect, 1f, 1f);
|
||||
AddVerticalQuad(colliderBuffers, br, bl, tl, tr, Rect.zero, 1f, 1f, false);
|
||||
AddVerticalQuad(renderBuffers, br, bl, tl, tr, width, height, textureLayer, Vector3.right);
|
||||
AddVerticalQuad(colliderBuffers, br, bl, tl, tr, width, height, 0, Vector3.right, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddVerticalQuad(renderBuffers, bl, br, tr, tl, uvRect, 1f, 1f);
|
||||
AddVerticalQuad(colliderBuffers, bl, br, tr, tl, Rect.zero, 1f, 1f, false);
|
||||
AddVerticalQuad(renderBuffers, bl, br, tr, tl, width, height, textureLayer, Vector3.left);
|
||||
AddVerticalQuad(colliderBuffers, bl, br, tr, tl, width, height, 0, Vector3.left, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTopQuad(MeshBuffers buffers, float x, float z, float width, float depth, float height, Rect uvRect, bool withUv = true)
|
||||
private bool TryGetNorthSouthFace(Vector2Int coord, int[] heights, byte[] biomeIndices, int x, int z, int y, bool north, out byte biomeIndex, out VoxelSurfaceType surfaceType, out int textureLayer)
|
||||
{
|
||||
int current = heights[z * chunkSize + x];
|
||||
int worldX = coord.x * chunkSize + x;
|
||||
int worldZ = coord.y * chunkSize + z;
|
||||
int neighbor = GetHeightAtWorldCell(new Vector2Int(worldX, north ? worldZ + 1 : worldZ - 1));
|
||||
if (y >= current || y < neighbor)
|
||||
{
|
||||
biomeIndex = 0;
|
||||
surfaceType = VoxelSurfaceType.Dirt;
|
||||
textureLayer = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
biomeIndex = biomeIndices[z * chunkSize + x];
|
||||
surfaceType = y == current - 1 ? VoxelSurfaceType.CliffSide : VoxelSurfaceType.Dirt;
|
||||
textureLayer = atlas.GetTextureLayer(biomeIndex, surfaceType);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryGetEastWestFace(Vector2Int coord, int[] heights, byte[] biomeIndices, int x, int z, int y, bool east, out byte biomeIndex, out VoxelSurfaceType surfaceType, out int textureLayer)
|
||||
{
|
||||
int current = heights[z * chunkSize + x];
|
||||
int worldX = coord.x * chunkSize + x;
|
||||
int worldZ = coord.y * chunkSize + z;
|
||||
int neighbor = GetHeightAtWorldCell(new Vector2Int(east ? worldX + 1 : worldX - 1, worldZ));
|
||||
if (y >= current || y < neighbor)
|
||||
{
|
||||
biomeIndex = 0;
|
||||
surfaceType = VoxelSurfaceType.Dirt;
|
||||
textureLayer = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
biomeIndex = biomeIndices[z * chunkSize + x];
|
||||
surfaceType = y == current - 1 ? VoxelSurfaceType.CliffSide : VoxelSurfaceType.Dirt;
|
||||
textureLayer = atlas.GetTextureLayer(biomeIndex, surfaceType);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AddTopQuad(MeshBuffers buffers, float x, float z, float width, float depth, float height, int textureLayer, bool withUv = true)
|
||||
{
|
||||
int baseIndex = buffers.Vertices.Count;
|
||||
buffers.Vertices.Add(new Vector3(x, height, z));
|
||||
buffers.Vertices.Add(new Vector3(x + width, height, z));
|
||||
buffers.Vertices.Add(new Vector3(x + width, height, z + depth));
|
||||
buffers.Vertices.Add(new Vector3(x, height, z + depth));
|
||||
buffers.Normals.Add(Vector3.up);
|
||||
buffers.Normals.Add(Vector3.up);
|
||||
buffers.Normals.Add(Vector3.up);
|
||||
buffers.Normals.Add(Vector3.up);
|
||||
|
||||
if (withUv)
|
||||
{
|
||||
buffers.Uvs.Add(new Vector2(uvRect.xMin, uvRect.yMin));
|
||||
buffers.Uvs.Add(new Vector2(uvRect.xMax, uvRect.yMin));
|
||||
buffers.Uvs.Add(new Vector2(uvRect.xMax, uvRect.yMax));
|
||||
buffers.Uvs.Add(new Vector2(uvRect.xMin, uvRect.yMax));
|
||||
buffers.Uvs.Add(new Vector2(0f, 0f));
|
||||
buffers.Uvs.Add(new Vector2(width, 0f));
|
||||
buffers.Uvs.Add(new Vector2(width, depth));
|
||||
buffers.Uvs.Add(new Vector2(0f, depth));
|
||||
Vector2 textureData = new Vector2(textureLayer, 0f);
|
||||
buffers.TextureData.Add(textureData);
|
||||
buffers.TextureData.Add(textureData);
|
||||
buffers.TextureData.Add(textureData);
|
||||
buffers.TextureData.Add(textureData);
|
||||
}
|
||||
|
||||
buffers.Triangles.Add(baseIndex);
|
||||
@@ -548,20 +752,29 @@ namespace InfiniteWorld.VoxelWorld
|
||||
buffers.Triangles.Add(baseIndex + 2);
|
||||
}
|
||||
|
||||
private void AddVerticalQuad(MeshBuffers buffers, Vector3 bottomLeft, Vector3 bottomRight, Vector3 topRight, Vector3 topLeft, Rect uvRect, float width, float height, bool withUv = true)
|
||||
private void AddVerticalQuad(MeshBuffers buffers, Vector3 bottomLeft, Vector3 bottomRight, Vector3 topRight, Vector3 topLeft, float width, float height, int textureLayer, Vector3 normal, bool withUv = true)
|
||||
{
|
||||
int baseIndex = buffers.Vertices.Count;
|
||||
buffers.Vertices.Add(bottomLeft);
|
||||
buffers.Vertices.Add(bottomRight);
|
||||
buffers.Vertices.Add(topRight);
|
||||
buffers.Vertices.Add(topLeft);
|
||||
buffers.Normals.Add(normal);
|
||||
buffers.Normals.Add(normal);
|
||||
buffers.Normals.Add(normal);
|
||||
buffers.Normals.Add(normal);
|
||||
|
||||
if (withUv)
|
||||
{
|
||||
buffers.Uvs.Add(new Vector2(uvRect.xMin, uvRect.yMin));
|
||||
buffers.Uvs.Add(new Vector2(uvRect.xMax, uvRect.yMin));
|
||||
buffers.Uvs.Add(new Vector2(uvRect.xMax, uvRect.yMax));
|
||||
buffers.Uvs.Add(new Vector2(uvRect.xMin, uvRect.yMax));
|
||||
buffers.Uvs.Add(new Vector2(0f, 0f));
|
||||
buffers.Uvs.Add(new Vector2(width, 0f));
|
||||
buffers.Uvs.Add(new Vector2(width, height));
|
||||
buffers.Uvs.Add(new Vector2(0f, height));
|
||||
Vector2 textureData = new Vector2(textureLayer, 0f);
|
||||
buffers.TextureData.Add(textureData);
|
||||
buffers.TextureData.Add(textureData);
|
||||
buffers.TextureData.Add(textureData);
|
||||
buffers.TextureData.Add(textureData);
|
||||
}
|
||||
|
||||
buffers.Triangles.Add(baseIndex);
|
||||
@@ -831,7 +1044,9 @@ namespace InfiniteWorld.VoxelWorld
|
||||
private sealed class MeshBuffers
|
||||
{
|
||||
public readonly List<Vector3> Vertices = new List<Vector3>(512);
|
||||
public readonly List<Vector3> Normals = new List<Vector3>(512);
|
||||
public readonly List<Vector2> Uvs = new List<Vector2>(512);
|
||||
public readonly List<Vector2> TextureData = new List<Vector2>(512);
|
||||
public readonly List<int> Triangles = new List<int>(1024);
|
||||
|
||||
public Mesh ToMesh(string meshName)
|
||||
@@ -844,12 +1059,19 @@ namespace InfiniteWorld.VoxelWorld
|
||||
|
||||
mesh.indexFormat = Vertices.Count > 65535 ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
|
||||
mesh.SetVertices(Vertices);
|
||||
if (Normals.Count == Vertices.Count)
|
||||
{
|
||||
mesh.SetNormals(Normals);
|
||||
}
|
||||
if (Uvs.Count == Vertices.Count)
|
||||
{
|
||||
mesh.SetUVs(0, Uvs);
|
||||
}
|
||||
if (TextureData.Count == Vertices.Count)
|
||||
{
|
||||
mesh.SetUVs(1, TextureData);
|
||||
}
|
||||
mesh.SetTriangles(Triangles, 0, true);
|
||||
mesh.RecalculateNormals();
|
||||
mesh.RecalculateBounds();
|
||||
return mesh;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d3a1e4bd82462b4790722c7ebbf2e17
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,70 @@
|
||||
Shader "Infinite World/VoxelWorld/TextureArrayUnlit"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
_TextureArray("Texture Array", 2DArray) = "white" {}
|
||||
_BaseColor("Base Color", Color) = (1, 1, 1, 1)
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags { "RenderType"="Opaque" "Queue"="Geometry" "RenderPipeline"="UniversalPipeline" }
|
||||
LOD 100
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "Forward"
|
||||
Tags { "LightMode"="SRPDefaultUnlit" }
|
||||
Cull Back
|
||||
ZWrite On
|
||||
|
||||
HLSLPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma target 3.5
|
||||
#pragma require 2darray
|
||||
|
||||
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
||||
|
||||
TEXTURE2D_ARRAY(_TextureArray);
|
||||
SAMPLER(sampler_TextureArray);
|
||||
|
||||
CBUFFER_START(UnityPerMaterial)
|
||||
half4 _BaseColor;
|
||||
CBUFFER_END
|
||||
|
||||
struct Attributes
|
||||
{
|
||||
float4 positionOS : POSITION;
|
||||
float3 normalOS : NORMAL;
|
||||
float2 uv : TEXCOORD0;
|
||||
float2 textureData : TEXCOORD1;
|
||||
};
|
||||
|
||||
struct Varyings
|
||||
{
|
||||
float4 positionHCS : SV_POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
float textureLayer : TEXCOORD1;
|
||||
};
|
||||
|
||||
Varyings vert(Attributes input)
|
||||
{
|
||||
Varyings output;
|
||||
VertexPositionInputs positionInputs = GetVertexPositionInputs(input.positionOS.xyz);
|
||||
output.positionHCS = positionInputs.positionCS;
|
||||
output.uv = input.uv;
|
||||
output.textureLayer = input.textureData.x;
|
||||
return output;
|
||||
}
|
||||
|
||||
half4 frag(Varyings input) : SV_Target
|
||||
{
|
||||
float2 tiledUv = frac(input.uv);
|
||||
half4 albedo = SAMPLE_TEXTURE2D_ARRAY(_TextureArray, sampler_TextureArray, tiledUv, input.textureLayer);
|
||||
return albedo * _BaseColor;
|
||||
}
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec80aebd8cb61f44cbfa6b7d5f087211
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -12,6 +12,7 @@
|
||||
"com.unity.2d.tooling": "1.0.2",
|
||||
"com.unity.burst": "1.8.28",
|
||||
"com.unity.collab-proxy": "2.11.3",
|
||||
"com.unity.collections": "2.6.2",
|
||||
"com.unity.ext.nunit": "2.0.5",
|
||||
"com.unity.feature.2d": "2.0.2",
|
||||
"com.unity.ide.rider": "3.0.39",
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
},
|
||||
"com.unity.collections": {
|
||||
"version": "2.6.2",
|
||||
"depth": 1,
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.burst": "1.8.23",
|
||||
@@ -211,7 +211,7 @@
|
||||
},
|
||||
"com.unity.nuget.mono-cecil": {
|
||||
"version": "1.11.6",
|
||||
"depth": 2,
|
||||
"depth": 1,
|
||||
"source": "registry",
|
||||
"dependencies": {},
|
||||
"url": "https://packages.unity.com"
|
||||
@@ -283,7 +283,7 @@
|
||||
},
|
||||
"com.unity.test-framework.performance": {
|
||||
"version": "3.2.0",
|
||||
"depth": 2,
|
||||
"depth": 1,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.test-framework": "1.1.33",
|
||||
|
||||
@@ -63,3 +63,4 @@
|
||||
| TASK-0021 | ToDo | High | architecture | unassigned | 2d | docs/tasks/items/TASK-0021.md | Привести проект в порядок: разнести код по asmdef, навести структуру Editor/Runtime и добавить базовые автотесты. |
|
||||
| TASK-0022 | ToDo | Highest | worldgen | unassigned | 1d | docs/tasks/items/TASK-0022.md | Интегрировать спавн врагов в VoxelWorldGenerator: спавнить по загрузке чанка и учитывать kill-state. |
|
||||
| TASK-0023 | ToDo | Highest | ai | unassigned | 2d | docs/tasks/items/TASK-0023.md | Реализовать runtime NavMesh bake для voxel-чанка и интегрировать обновление навигации при загрузке/изменении чанков. |
|
||||
| TASK-0024 | ToDo | Highest | art | unassigned | 2d | docs/tasks/items/TASK-0024.md | Заменить Minecraft-placeholder арт на легальные ассеты для продакшена и зафиксировать источник/лицензии. |
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
id: TASK-0024
|
||||
title: Заменить Minecraft-placeholder арт на легальные ассеты
|
||||
summary: Убрать заглушки из Minecraft текстурпака (нет прав для продакшена), заменить на легально используемые ассеты и зафиксировать источники/лицензии.
|
||||
priority: Highest
|
||||
area: art
|
||||
owner: unassigned
|
||||
created: 2026-03-31
|
||||
updated: 2026-03-31
|
||||
execution_time: 2d
|
||||
depends_on: []
|
||||
canonical_docs:
|
||||
- docs/tasks/Index.md
|
||||
related_files:
|
||||
- Assets/
|
||||
---
|
||||
|
||||
# TASK-0024 - Заменить Minecraft-placeholder арт на легальные ассеты
|
||||
|
||||
## Status
|
||||
|
||||
Статус задачи ведется в `docs/tasks/Index.md` и является каноническим там.
|
||||
|
||||
## Why
|
||||
|
||||
Сейчас в проекте используются заглушки из Minecraft текстурпака. Прав на их использование в продакшене нет, это юридический и релизный риск.
|
||||
|
||||
## Expected Outcome
|
||||
|
||||
- В проекте не осталось ассетов из Minecraft текстурпака.
|
||||
- Все используемые визуальные ассеты имеют разрешенный источник (собственные/купленные/CC0/лицензия совместима с коммерческим использованием).
|
||||
- В репозитории есть короткая фиксация: откуда ассеты, какие лицензии, где лежат файлы.
|
||||
|
||||
## Current Context
|
||||
|
||||
Воксельный рендер использует атлас/материал. Замена арта должна сохранить текущий контракт данных (например, набор surface types), чтобы не сломать генератор.
|
||||
|
||||
## Source Of Truth
|
||||
|
||||
- фактические ассеты в `Assets/`
|
||||
- файлы лицензий/README от поставщика ассетов
|
||||
- документ со списком источников и лицензий (создать в рамках задачи)
|
||||
|
||||
## Read First
|
||||
|
||||
- `Assets/Scripts/VoxelWorld/Runtime/VoxelWorldAtlas.cs`
|
||||
- `Assets/Scripts/VoxelWorld/Runtime/VoxelWorldGenerator.cs`
|
||||
|
||||
## Scope In
|
||||
|
||||
- инвентаризация: какие текстуры/материалы сейчас заглушки
|
||||
- удаление/замена заглушек на легальные аналоги
|
||||
- обновление атласа/материалов/спрайтов так, чтобы мир продолжал рендериться
|
||||
- добавление файла `docs/licenses/art-assets.md` (или эквивалента) со списком источников и лицензий
|
||||
|
||||
## Scope Out
|
||||
|
||||
- финальный художественный стиль игры
|
||||
- полноценный арт-пайплайн (если не требуется прямо сейчас)
|
||||
|
||||
## Constraints
|
||||
|
||||
- не добавлять ассеты с неясной лицензией
|
||||
- предпочтительно: CC0/покупные с подтверждением/собственные
|
||||
- не ломать текущие сцены и генерацию мира
|
||||
|
||||
## Suggested Approach
|
||||
|
||||
1. Найти все текстуры/материалы, пришедшие из Minecraft текстурпака.
|
||||
2. Выбрать источник замены (CC0 pack или собственные временные ассеты) и добавить их в проект.
|
||||
3. Обновить атлас/материал и проверить рендер чанков.
|
||||
4. Зафиксировать источники и лицензии в отдельном документе.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- в проекте нет Minecraft-placeholder ассетов
|
||||
- мир рендерится корректно после замены
|
||||
- есть документированная таблица "asset -> источник -> лицензия"
|
||||
|
||||
## Verification
|
||||
|
||||
- ручная проверка в сцене: генерация чанков, отображение поверхностей
|
||||
- grep/поиск по репозиторию по ключевым словам/именам, связанным с Minecraft pack
|
||||
|
||||
## Risks / Open Questions
|
||||
|
||||
- часть заглушек может быть уже запечена в атлас; нужно аккуратно заменить без поломки UV
|
||||
|
||||
## Human Decisions Needed
|
||||
|
||||
- выбрать конкретный источник легальных ассетов (CC0 pack / купленные / собственные)
|
||||
|
||||
## Decision Log
|
||||
|
||||
- `2026-03-31` - задача добавлена из-за отсутствия прав на текущий placeholder текстурпак.
|
||||
|
||||
## Handoff Notes
|
||||
|
||||
Если будут добавляться сторонние ассеты, сохраняйте рядом с ними LICENSE/README от автора или ссылку на источник.
|
||||
Reference in New Issue
Block a user