Second commit
This commit is contained in:
commit
36840f3ad4
999 files changed
+61364
No files matched your search
@@ -0,0 +1,11 @@
|
||||
using R3;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.D.Cleaning
|
||||
{
|
||||
public class Cleanable : MonoBehaviour
|
||||
{
|
||||
public ReactiveProperty<bool> Cleaned { get; } = new();
|
||||
public ReactiveProperty<float> CleanProgress { get; } = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 219620ccf64f42d7a64d27a3a152f961
|
||||
timeCreated: 1785923122
|
||||
@@ -0,0 +1,35 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.D.Cleaning
|
||||
{
|
||||
public class CleanableAsset : Cleanable
|
||||
{
|
||||
private Collider _dirtyCollider;
|
||||
private static readonly int MaskMap = Shader.PropertyToID("_Mask");
|
||||
[SerializeField] private float cleanCountPerFrame;
|
||||
private Material _renderMaterial;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_dirtyCollider = GetComponent<Collider>();
|
||||
|
||||
_renderMaterial = GetComponent<MeshRenderer>().material;
|
||||
}
|
||||
private void AssumedAsCleaned()
|
||||
{
|
||||
Cleaned.Value = true;
|
||||
_dirtyCollider.enabled = false;
|
||||
_renderMaterial.SetTexture(MaskMap, Texture2D.blackTexture);
|
||||
}
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
CleanProgress.Value += cleanCountPerFrame;
|
||||
|
||||
if(CleanProgress.CurrentValue >= 100)
|
||||
AssumedAsCleaned();
|
||||
|
||||
Debug.Log($"{name} прогресс очистки: {CleanProgress.CurrentValue}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b184056d80d64c5d9f9cdb03cb00efc7
|
||||
timeCreated: 1785923122
|
||||
@@ -0,0 +1,490 @@
|
||||
using System.Collections.Generic;
|
||||
using Code.D.Cleaning.CleanTools;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using TinyGoose.Tremble;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace Code.D.Cleaning
|
||||
{
|
||||
[BrushEntity("CleanableSurface", layer: "Cleanable")]
|
||||
public class CleanableSurface : Cleanable, IOnImportFromMapEntity
|
||||
{
|
||||
private static readonly int MaskMap = Shader.PropertyToID("_Mask");
|
||||
private static readonly int DrawPosition = Shader.PropertyToID("_DrawPosition");
|
||||
private static readonly int DrawRadius = Shader.PropertyToID("_DrawRadius");
|
||||
private static readonly int ToolDirtiness = Shader.PropertyToID("_ToolDirtiness");
|
||||
private static readonly int BrushTex = Shader.PropertyToID("_BrushTex");
|
||||
private static readonly int DrawAngleX = Shader.PropertyToID("_DrawAngleX");
|
||||
private static readonly int DrawAngleY = Shader.PropertyToID("_DrawAngleY");
|
||||
|
||||
private const int CalcResolution = 64;
|
||||
|
||||
[SerializeField] private float cleanedThreshold = 0.7f;
|
||||
|
||||
private RenderTexture _maskRT;
|
||||
private RenderTexture _calcRT;
|
||||
private Material _brushMaterial;
|
||||
private Material _renderMaterial;
|
||||
|
||||
private bool _onProcess;
|
||||
private UniTask _calculationOperation;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
gameObject.tag = "CleanableSurface";
|
||||
|
||||
_maskRT = new RenderTexture(1024, 1024, 0, RenderTextureFormat.ARGB32);
|
||||
_maskRT.Create();
|
||||
Graphics.Blit(Texture2D.whiteTexture, _maskRT);
|
||||
|
||||
_calcRT = new RenderTexture(CalcResolution, CalcResolution, 0, RenderTextureFormat.ARGB32);
|
||||
_calcRT.Create();
|
||||
|
||||
_brushMaterial = new Material(Shader.Find("Unlit/BrushPainter"));
|
||||
|
||||
if (!TryGetComponent<MeshRenderer>(out var renderer)) return;
|
||||
_renderMaterial = renderer.material;
|
||||
_renderMaterial.SetTexture(MaskMap, _maskRT);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Нейросеть написала и этот метод, слишком много математики мне он не нравится. Он заставляет игру лагать. Нужно сделать проще.
|
||||
/// Хотябы комментарии оставлены человеком.
|
||||
/// Я профнепригоден.
|
||||
/// </summary>
|
||||
/// <param name="hit"></param>
|
||||
/// <param name="tryapkaUsageInfo"></param>
|
||||
/// <param name="playerCamera"></param>
|
||||
public void Clean(RaycastHit hit, TryapkaUsageInfo tryapkaUsageInfo, Transform playerCamera)
|
||||
{
|
||||
if (Cleaned.CurrentValue) return;
|
||||
|
||||
Vector2 uv = hit.textureCoord;
|
||||
|
||||
// Логика вычисления направления кисти
|
||||
Vector3 dirToCamera = playerCamera.position - hit.point;
|
||||
Vector3 projDir = Vector3.ProjectOnPlane(dirToCamera, hit.normal);
|
||||
|
||||
if (projDir.sqrMagnitude < 0.0001f)
|
||||
{
|
||||
projDir = Vector3.ProjectOnPlane(playerCamera.up, hit.normal);
|
||||
}
|
||||
|
||||
projDir.Normalize();
|
||||
|
||||
// Логика определения U V или W
|
||||
Vector3 localDir = hit.transform.InverseTransformDirection(projDir);
|
||||
Vector3 localNormal = hit.transform.InverseTransformDirection(hit.normal).normalized;
|
||||
|
||||
ComputeTangentBasis(localNormal, out var axisU, out var axisV);
|
||||
|
||||
// Определяем синус и косинус поворота
|
||||
float u = Vector3.Dot(localDir, axisU);
|
||||
float v = Vector3.Dot(localDir, axisV);
|
||||
|
||||
_brushMaterial.SetVector(DrawPosition, uv);
|
||||
_brushMaterial.SetFloat(DrawRadius, tryapkaUsageInfo.Radius);
|
||||
_brushMaterial.SetFloat(DrawAngleX, v);
|
||||
_brushMaterial.SetFloat(DrawAngleY, u);
|
||||
_brushMaterial.SetFloat(ToolDirtiness, tryapkaUsageInfo.Dirtiness);
|
||||
_brushMaterial.SetTexture(BrushTex, tryapkaUsageInfo.BrushTexture);
|
||||
|
||||
Graphics.Blit(Texture2D.blackTexture, _maskRT, _brushMaterial, 0);
|
||||
|
||||
if (_calculationOperation.Status != UniTaskStatus.Pending)
|
||||
{
|
||||
_calculationOperation = CalculateCleanPercentage();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ComputeTangentBasis(Vector3 normal, out Vector3 axisU, out Vector3 axisV)
|
||||
{
|
||||
if (Mathf.Abs(normal.y) > FlatNormalThreshold)
|
||||
{
|
||||
axisU = Vector3.right;
|
||||
axisV = Vector3.forward;
|
||||
}
|
||||
else
|
||||
{
|
||||
axisU = Vector3.Cross(Vector3.up, normal).normalized;
|
||||
axisV = Vector3.up;
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask CalculateCleanPercentage()
|
||||
{
|
||||
if (!_maskRT || _onProcess) return;
|
||||
|
||||
_onProcess = true;
|
||||
|
||||
Graphics.Blit(_maskRT, _calcRT);
|
||||
|
||||
var destroyToken = this.GetCancellationTokenOnDestroy();
|
||||
var request = AsyncGPUReadback.Request(_calcRT, 0, TextureFormat.RGBA32);
|
||||
await request.ToUniTask(cancellationToken: destroyToken);
|
||||
|
||||
if (request.hasError)
|
||||
{
|
||||
_onProcess = false;
|
||||
return;
|
||||
}
|
||||
|
||||
NativeArray<byte> data = request.GetData<byte>();
|
||||
int totalBytes = data.Length;
|
||||
long totalBrightness = 0;
|
||||
|
||||
for (var i = 0; i < totalBytes; i += 4)
|
||||
{
|
||||
totalBrightness += data[i];
|
||||
}
|
||||
|
||||
int pixelCount = totalBytes / 4;
|
||||
float maxPossibleBrightness = pixelCount * 255f;
|
||||
|
||||
float progress = 1.0f - (totalBrightness / maxPossibleBrightness);
|
||||
|
||||
if (Mathf.Abs(progress - CleanProgress.CurrentValue) > 0.01f)
|
||||
{
|
||||
CleanProgress.Value = progress;
|
||||
}
|
||||
|
||||
if (CleanProgress.CurrentValue >= cleanedThreshold && !Cleaned.CurrentValue)
|
||||
{
|
||||
AssumedAsCleaned();
|
||||
}
|
||||
|
||||
_onProcess = false;
|
||||
}
|
||||
|
||||
private void AssumedAsCleaned()
|
||||
{
|
||||
Cleaned.Value = true;
|
||||
FinishClean();
|
||||
}
|
||||
|
||||
private void FinishClean()
|
||||
{
|
||||
Graphics.Blit(Texture2D.blackTexture, _maskRT);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_maskRT) _maskRT.Release();
|
||||
if (_calcRT) _calcRT.Release();
|
||||
if (_brushMaterial) Destroy(_brushMaterial);
|
||||
if (_renderMaterial) Destroy(_renderMaterial);
|
||||
}
|
||||
|
||||
public void OnImportFromMapEntity(MapBsp mapBsp, BspEntity entity)
|
||||
{
|
||||
Debug.Log("Found a brush!");
|
||||
|
||||
var meshFilter = gameObject.GetComponent<MeshFilter>();
|
||||
if (meshFilter == null)
|
||||
{
|
||||
Debug.LogError($"[CleanableSurface] MeshFilter отсутствует на {gameObject.name}");
|
||||
return;
|
||||
}
|
||||
|
||||
var sourceMesh = meshFilter.sharedMesh;
|
||||
if (sourceMesh == null)
|
||||
{
|
||||
Debug.LogError($"[CleanableSurface] sharedMesh == null на {gameObject.name}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sourceMesh.isReadable)
|
||||
{
|
||||
Debug.LogError($"[CleanableSurface] Меш '{sourceMesh.name}' не readable, не могу построить UV");
|
||||
return;
|
||||
}
|
||||
|
||||
if (sourceMesh.vertexCount == 0 || sourceMesh.triangles.Length == 0)
|
||||
{
|
||||
Debug.LogError($"[CleanableSurface] Меш '{sourceMesh.name}' пустой");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
BuildLayeredUVInPlace(sourceMesh);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogError($"[CleanableSurface] Исключение при построении UV для {gameObject.name}: {ex}");
|
||||
return;
|
||||
}
|
||||
|
||||
var meshCollider = gameObject.GetComponent<MeshCollider>();
|
||||
if (meshCollider != null)
|
||||
meshCollider.sharedMesh = sourceMesh;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
var existingFlags = GameObjectUtility.GetStaticEditorFlags(gameObject);
|
||||
GameObjectUtility.SetStaticEditorFlags(gameObject, existingFlags | StaticEditorFlags.ContributeGI);
|
||||
#endif
|
||||
|
||||
Debug.Log(
|
||||
$"[CleanableSurface] UV построен для {gameObject.name}: verts={sourceMesh.vertexCount}, tris={sourceMesh.triangles.Length / 3}");
|
||||
}
|
||||
|
||||
private const float MaskUvPadding = 0.08f;
|
||||
|
||||
private const float WeldEpsilon = 0.0005f;
|
||||
|
||||
private const float CoplanarDotThreshold = 0.999f;
|
||||
|
||||
private const float FlatNormalThreshold = 0.999f;
|
||||
|
||||
/// <summary>
|
||||
/// Этот большой и сложный метод написан нейросетью Claude Sonnet 5 Medium. Я профнепригоден
|
||||
/// </summary>
|
||||
/// <param name="mesh"></param>
|
||||
private static void BuildLayeredUVInPlace(Mesh mesh)
|
||||
{
|
||||
var srcVerts = mesh.vertices;
|
||||
var srcNormals = mesh.normals;
|
||||
var srcUvTile = mesh.uv;
|
||||
bool hasNormals = srcNormals.Length == srcVerts.Length;
|
||||
bool hasUvTile = srcUvTile.Length == srcVerts.Length;
|
||||
|
||||
int subMeshCount = mesh.subMeshCount;
|
||||
|
||||
var posToId = new Dictionary<Vector3Int, int>();
|
||||
|
||||
int CanonicalId(Vector3 p)
|
||||
{
|
||||
var key = new Vector3Int(
|
||||
Mathf.RoundToInt(p.x / WeldEpsilon),
|
||||
Mathf.RoundToInt(p.y / WeldEpsilon),
|
||||
Mathf.RoundToInt(p.z / WeldEpsilon));
|
||||
if (posToId.TryGetValue(key, out var id)) return id;
|
||||
id = posToId.Count;
|
||||
posToId[key] = id;
|
||||
return id;
|
||||
}
|
||||
|
||||
var triSubMesh = new List<int>();
|
||||
var triI0 = new List<int>();
|
||||
var triI1 = new List<int>();
|
||||
var triI2 = new List<int>();
|
||||
var triCanonA = new List<int>();
|
||||
var triCanonB = new List<int>();
|
||||
var triCanonC = new List<int>();
|
||||
var triNormal = new List<Vector3>();
|
||||
|
||||
for (int s = 0; s < subMeshCount; s++)
|
||||
{
|
||||
var tris = mesh.GetTriangles(s);
|
||||
for (int t = 0; t < tris.Length; t += 3)
|
||||
{
|
||||
int i0 = tris[t], i1 = tris[t + 1], i2 = tris[t + 2];
|
||||
Vector3 p0 = srcVerts[i0], p1 = srcVerts[i1], p2 = srcVerts[i2];
|
||||
Vector3 n = Vector3.Cross(p1 - p0, p2 - p0);
|
||||
n = n.sqrMagnitude > 1e-10f ? n.normalized : Vector3.up;
|
||||
|
||||
triSubMesh.Add(s);
|
||||
triI0.Add(i0);
|
||||
triI1.Add(i1);
|
||||
triI2.Add(i2);
|
||||
triCanonA.Add(CanonicalId(p0));
|
||||
triCanonB.Add(CanonicalId(p1));
|
||||
triCanonC.Add(CanonicalId(p2));
|
||||
triNormal.Add(n);
|
||||
}
|
||||
}
|
||||
|
||||
int triCount = triSubMesh.Count;
|
||||
|
||||
var parent = new int[triCount];
|
||||
for (int i = 0; i < triCount; i++) parent[i] = i;
|
||||
|
||||
int Find(int x)
|
||||
{
|
||||
while (parent[x] != x)
|
||||
{
|
||||
parent[x] = parent[parent[x]];
|
||||
x = parent[x];
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
void Union(int a, int b)
|
||||
{
|
||||
a = Find(a);
|
||||
b = Find(b);
|
||||
if (a != b) parent[a] = b;
|
||||
}
|
||||
|
||||
var edgeMap = new Dictionary<long, int>();
|
||||
|
||||
long PackEdge(int a, int b)
|
||||
{
|
||||
if (a > b) (a, b) = (b, a);
|
||||
return ((long)a << 32) | (uint)b;
|
||||
}
|
||||
|
||||
void ProcessEdge(long e, int t)
|
||||
{
|
||||
if (edgeMap.TryGetValue(e, out var other))
|
||||
{
|
||||
if (Vector3.Dot(triNormal[t], triNormal[other]) > CoplanarDotThreshold)
|
||||
Union(t, other);
|
||||
}
|
||||
else
|
||||
{
|
||||
edgeMap[e] = t;
|
||||
}
|
||||
}
|
||||
|
||||
for (int t = 0; t < triCount; t++)
|
||||
{
|
||||
int a = triCanonA[t], b = triCanonB[t], c = triCanonC[t];
|
||||
ProcessEdge(PackEdge(a, b), t);
|
||||
ProcessEdge(PackEdge(b, c), t);
|
||||
ProcessEdge(PackEdge(c, a), t);
|
||||
}
|
||||
|
||||
var islandOf = new int[triCount];
|
||||
var islandRootToIndex = new Dictionary<int, int>();
|
||||
for (int t = 0; t < triCount; t++)
|
||||
{
|
||||
int root = Find(t);
|
||||
if (!islandRootToIndex.TryGetValue(root, out var idx))
|
||||
{
|
||||
idx = islandRootToIndex.Count;
|
||||
islandRootToIndex[root] = idx;
|
||||
}
|
||||
|
||||
islandOf[t] = idx;
|
||||
}
|
||||
|
||||
int islandCount = islandRootToIndex.Count;
|
||||
|
||||
var accumNormal = new Vector3[islandCount];
|
||||
for (int t = 0; t < triCount; t++)
|
||||
accumNormal[islandOf[t]] += triNormal[t];
|
||||
|
||||
var islandAxisU = new Vector3[islandCount];
|
||||
var islandAxisV = new Vector3[islandCount];
|
||||
for (int i = 0; i < islandCount; i++)
|
||||
{
|
||||
var n = accumNormal[i].normalized;
|
||||
ComputeTangentBasis(n, out islandAxisU[i], out islandAxisV[i]);
|
||||
}
|
||||
|
||||
var islandMinU = new float[islandCount];
|
||||
var islandMinV = new float[islandCount];
|
||||
var islandMaxU = new float[islandCount];
|
||||
var islandMaxV = new float[islandCount];
|
||||
for (int i = 0; i < islandCount; i++)
|
||||
{
|
||||
islandMinU[i] = islandMinV[i] = float.PositiveInfinity;
|
||||
islandMaxU[i] = islandMaxV[i] = float.NegativeInfinity;
|
||||
}
|
||||
|
||||
Vector2 ProjectUV(Vector3 local, Vector3 axisU, Vector3 axisV) =>
|
||||
new Vector2(Vector3.Dot(local, axisU), Vector3.Dot(local, axisV));
|
||||
|
||||
for (int t = 0; t < triCount; t++)
|
||||
{
|
||||
int island = islandOf[t];
|
||||
Vector2 a = ProjectUV(srcVerts[triI0[t]], islandAxisU[island], islandAxisV[island]);
|
||||
Vector2 b = ProjectUV(srcVerts[triI1[t]], islandAxisU[island], islandAxisV[island]);
|
||||
Vector2 c = ProjectUV(srcVerts[triI2[t]], islandAxisU[island], islandAxisV[island]);
|
||||
|
||||
islandMinU[island] = Mathf.Min(islandMinU[island], a.x, b.x, c.x);
|
||||
islandMinV[island] = Mathf.Min(islandMinV[island], a.y, b.y, c.y);
|
||||
islandMaxU[island] = Mathf.Max(islandMaxU[island], a.x, b.x, c.x);
|
||||
islandMaxV[island] = Mathf.Max(islandMaxV[island], a.y, b.y, c.y);
|
||||
}
|
||||
|
||||
int gridSize = Mathf.Max(1, Mathf.CeilToInt(Mathf.Sqrt(islandCount)));
|
||||
float cellSize = 1f / gridSize;
|
||||
float cellPad = cellSize * MaskUvPadding * 0.5f;
|
||||
|
||||
var islandCellOrigin = new Vector2[islandCount];
|
||||
var islandScale = new float[islandCount];
|
||||
for (int i = 0; i < islandCount; i++)
|
||||
{
|
||||
int col = i % gridSize;
|
||||
int row = i / gridSize;
|
||||
islandCellOrigin[i] = new Vector2(col * cellSize, row * cellSize);
|
||||
|
||||
float span = Mathf.Max(Mathf.Max(islandMaxU[i] - islandMinU[i], islandMaxV[i] - islandMinV[i]), 1e-5f);
|
||||
float usableCell = cellSize - cellPad * 2f;
|
||||
islandScale[i] = usableCell / span;
|
||||
}
|
||||
|
||||
var newVerts = new List<Vector3>();
|
||||
var newNormals = new List<Vector3>();
|
||||
var newUvMask = new List<Vector2>();
|
||||
var newUvTile = new List<Vector2>();
|
||||
var subTriangleLists = new List<int>[subMeshCount];
|
||||
for (int s = 0; s < subMeshCount; s++) subTriangleLists[s] = new List<int>();
|
||||
|
||||
for (int t = 0; t < triCount; t++)
|
||||
{
|
||||
int island = islandOf[t];
|
||||
|
||||
Vector2 MapAtlas(Vector3 local)
|
||||
{
|
||||
Vector2 uv = ProjectUV(local, islandAxisU[island], islandAxisV[island]);
|
||||
Vector2 norm = new Vector2(uv.x - islandMinU[island], uv.y - islandMinV[island]) *
|
||||
islandScale[island];
|
||||
return islandCellOrigin[island] + new Vector2(cellPad, cellPad) + norm;
|
||||
}
|
||||
|
||||
int i0 = triI0[t], i1 = triI1[t], i2 = triI2[t];
|
||||
Vector3 p0 = srcVerts[i0], p1 = srcVerts[i1], p2 = srcVerts[i2];
|
||||
|
||||
int baseIndex = newVerts.Count;
|
||||
newVerts.Add(p0);
|
||||
newVerts.Add(p1);
|
||||
newVerts.Add(p2);
|
||||
newNormals.Add(hasNormals ? srcNormals[i0] : triNormal[t]);
|
||||
newNormals.Add(hasNormals ? srcNormals[i1] : triNormal[t]);
|
||||
newNormals.Add(hasNormals ? srcNormals[i2] : triNormal[t]);
|
||||
|
||||
newUvMask.Add(MapAtlas(p0));
|
||||
newUvMask.Add(MapAtlas(p1));
|
||||
newUvMask.Add(MapAtlas(p2));
|
||||
|
||||
newUvTile.Add(hasUvTile ? srcUvTile[i0] : Vector2.zero);
|
||||
newUvTile.Add(hasUvTile ? srcUvTile[i1] : Vector2.zero);
|
||||
newUvTile.Add(hasUvTile ? srcUvTile[i2] : Vector2.zero);
|
||||
|
||||
int subMesh = triSubMesh[t];
|
||||
subTriangleLists[subMesh].Add(baseIndex);
|
||||
subTriangleLists[subMesh].Add(baseIndex + 1);
|
||||
subTriangleLists[subMesh].Add(baseIndex + 2);
|
||||
}
|
||||
|
||||
mesh.Clear();
|
||||
mesh.indexFormat = newVerts.Count > 65000
|
||||
? UnityEngine.Rendering.IndexFormat.UInt32
|
||||
: UnityEngine.Rendering.IndexFormat.UInt16;
|
||||
mesh.SetVertices(newVerts);
|
||||
mesh.SetNormals(newNormals);
|
||||
mesh.SetUVs(0, newUvMask);
|
||||
mesh.SetUVs(2, newUvTile);
|
||||
mesh.subMeshCount = subMeshCount;
|
||||
for (int s = 0; s < subMeshCount; s++)
|
||||
mesh.SetTriangles(subTriangleLists[s], s);
|
||||
|
||||
mesh.RecalculateBounds();
|
||||
if (!hasNormals) mesh.RecalculateNormals();
|
||||
mesh.RecalculateTangents();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
Unwrapping.GenerateSecondaryUVSet(mesh);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a09905e536314ef1bd4c016d3b5cfb76
|
||||
timeCreated: 1785923052
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using R3;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.D.Cleaning
|
||||
{
|
||||
public interface ICleaningService
|
||||
{
|
||||
}
|
||||
|
||||
public class CleaningService : IDisposable, ICleaningService
|
||||
{
|
||||
private List<Cleanable> _cleanableSurfaces = new();
|
||||
|
||||
private IDisposable _surfaces;
|
||||
|
||||
public ReactiveProperty<bool> LevelCleaned { get; } = new();
|
||||
|
||||
public CleaningService(Cleanable[] cleanableSurfaces)
|
||||
{
|
||||
DisposableBuilder builder = new DisposableBuilder();
|
||||
|
||||
foreach (var cleanable in cleanableSurfaces)
|
||||
{
|
||||
_cleanableSurfaces.Add(cleanable);
|
||||
builder.Add(cleanable.Cleaned
|
||||
.Subscribe(x => CheckProgress(cleanable)));
|
||||
}
|
||||
|
||||
_surfaces = builder.Build();
|
||||
}
|
||||
|
||||
private void CheckProgress(Cleanable cleanable)
|
||||
{
|
||||
if(cleanable.Cleaned.CurrentValue) Debug.Log($"{cleanable.name} cleaned");
|
||||
|
||||
CheckOverallProgress();
|
||||
}
|
||||
|
||||
private void CheckOverallProgress()
|
||||
{
|
||||
if (LevelCleaned.CurrentValue)
|
||||
return;
|
||||
|
||||
foreach (var t in _cleanableSurfaces)
|
||||
if (!t.Cleaned.CurrentValue) return;
|
||||
|
||||
Debug.Log($"VICTORY!!!");
|
||||
LevelCleaned.Value = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_surfaces?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5ce468dffcf438d98691a527f9c51ba
|
||||
timeCreated: 1785923122
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a45200a1a0d48beaea636b4b794ed95
|
||||
timeCreated: 1785925560
|
||||
@@ -0,0 +1,85 @@
|
||||
Shader "Unlit/BrushPainter"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
_DrawPosition("Draw Position", Vector) = (0.5, 0.5, 0, 0)
|
||||
_DrawRadius("Draw Radius", Float) = 0.05
|
||||
_DrawAngleX("Draw Angle (Rad) X", Float) = 0.0
|
||||
_DrawAngleY("Draw Angle (Rad) Y", Float) = 0.0
|
||||
_ToolDirtiness("Tool Dirtiness", Range(0, 1)) = 0
|
||||
[MainTexture] _BrushTex("Brush Texture", 2D) = "white" {}
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"RenderType" = "Transparent"
|
||||
"Queue" = "Transparent"
|
||||
}
|
||||
|
||||
Pass
|
||||
{
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
ZWrite Off
|
||||
ZTest Always
|
||||
|
||||
HLSLPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
|
||||
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
||||
|
||||
struct Attributes
|
||||
{
|
||||
float4 positionOS : POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
};
|
||||
|
||||
struct Varyings
|
||||
{
|
||||
float4 positionHCS : SV_POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
};
|
||||
|
||||
float4 _DrawPosition;
|
||||
float _DrawRadius;
|
||||
float _DrawAngleX;
|
||||
float _DrawAngleY;
|
||||
float _ToolDirtiness;
|
||||
|
||||
TEXTURE2D(_BrushTex);
|
||||
SAMPLER(sampler_BrushTex);
|
||||
|
||||
Varyings vert(Attributes IN)
|
||||
{
|
||||
Varyings OUT;
|
||||
OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
|
||||
OUT.uv = IN.uv;
|
||||
return OUT;
|
||||
}
|
||||
|
||||
half4 frag(Varyings IN) : SV_Target
|
||||
{
|
||||
float2 centeredUV = (IN.uv - _DrawPosition.xy) / (_DrawRadius * 2.0);
|
||||
|
||||
float2 rotatedUV;
|
||||
rotatedUV.x = centeredUV.x * _DrawAngleX - centeredUV.y * _DrawAngleY;
|
||||
rotatedUV.y = centeredUV.x * _DrawAngleY + centeredUV.y * _DrawAngleX;
|
||||
|
||||
float2 brushUV = rotatedUV + 0.5;
|
||||
|
||||
float inBounds = step(0.0, brushUV.x) * step(brushUV.x, 1.0) *
|
||||
step(0.0, brushUV.y) * step(brushUV.y, 1.0);
|
||||
|
||||
half4 brushColor = SAMPLE_TEXTURE2D(_BrushTex, sampler_BrushTex, brushUV);
|
||||
|
||||
float brushMask = brushColor.a * inBounds;
|
||||
float fillValue = _ToolDirtiness * brushMask;
|
||||
|
||||
return half4(fillValue, fillValue, fillValue, brushMask);
|
||||
}
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31f42842d3a8460ea125575cb5a72a5a
|
||||
timeCreated: 1785925570
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5e608514d754bcfb44c3df7c722601d
|
||||
timeCreated: 1786025274
|
||||
@@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.D.Cleaning.UI
|
||||
{
|
||||
public abstract class CleanToolWidget : MonoBehaviour
|
||||
{
|
||||
public abstract void SetToolDirtiness(float dirtiness);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95907fda6bb942a1865c6f1d9a6c1f91
|
||||
timeCreated: 1786025360
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Code.D.Cleaning.CleanTools;
|
||||
using Code.Infrastructure.UI;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Reflex.Attributes;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.D.Cleaning.UI
|
||||
{
|
||||
public class CleaningPanel : UIPanel
|
||||
{
|
||||
[Inject]
|
||||
private void Construct(List<CleanTool> cleanTools)
|
||||
{
|
||||
Debug.Log("InjectingPanel");
|
||||
|
||||
foreach (var cleanTool in cleanTools) Debug.Log(cleanTool);
|
||||
}
|
||||
|
||||
public override UniTask Open(CancellationToken cts = default)
|
||||
{
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public override UniTask Close(CancellationToken cts = default)
|
||||
{
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f7be6d67e6944899ab2c3ae0255b58e
|
||||
timeCreated: 1786025297
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Code.D.Cleaning.CleanTools;
|
||||
using R3;
|
||||
using Reflex.Attributes;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Code.D.Cleaning.UI
|
||||
{
|
||||
public class TryapkaWidget : CleanToolWidget
|
||||
{
|
||||
[SerializeField] private Image toolImage;
|
||||
[SerializeField] private Color cleanToolColor;
|
||||
[SerializeField] private Color dirtyToolColor;
|
||||
|
||||
private IDisposable _disposable;
|
||||
|
||||
[Inject]
|
||||
private void Construct(List<CleanTool> cleanTools)
|
||||
{
|
||||
Debug.Log("InjectingWidget");
|
||||
|
||||
var tool = cleanTools.SingleOrDefault(x => x.ToolType == CleanToolType.Tryapka);
|
||||
|
||||
if (tool == null) return;
|
||||
|
||||
_disposable = tool.CurrentDirtiness
|
||||
.Subscribe(SetToolDirtiness);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_disposable?.Dispose();
|
||||
}
|
||||
|
||||
public override void SetToolDirtiness(float dirtiness)
|
||||
{
|
||||
toolImage.color = Color.Lerp(cleanToolColor, dirtyToolColor, dirtiness);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6cbf9cd4573e4544a67318da5d5fa072
|
||||
timeCreated: 1786025415
|
||||
Reference in new issue
Block a user