Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions Components/Tests/ChiselCylinderUVTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
using System.Collections.Generic;
using Chisel.Core;
using NUnit.Framework;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace Chisel.Components.Tests
{
// A cylinder lays out its uvs (caps centered, sides one after another round it) on top of each surface's own uvs, so
// changing a surface's uvs, or the cylinder's settings, keeps both
[TestFixture]
public class ChiselCylinderUVTests
{
readonly List<GameObject> roots = new();
(Scene scene, bool wasDirty) remembered;

[SetUp]
public void SetUp()
{
remembered = SceneLeftClean.Remember();
}

[TearDown]
public void TearDown()
{
foreach (var root in roots)
{
if (root)
Object.DestroyImmediate(root);
}
roots.Clear();
Update();
SceneLeftClean.Restore(remembered);
}

static void Update()
{
for (int i = 0; i < 3; i++)
{
ChiselNodeHierarchyManager.Update();
ChiselModelManager.Instance.UpdateModels();
}
}

ChiselCylinderComponent Cylinder()
{
var model = new GameObject("model").AddComponent<ChiselModelComponent>();
roots.Add(model.gameObject);
var cylinder = new GameObject("cylinder").AddComponent<ChiselCylinderComponent>();
cylinder.transform.SetParent(model.transform, false);
Update();
return cylinder;
}

// The layout a cylinder makes, as it was written into its surfaces before: caps centered, side i offset along u
static UVMatrix Layout(in ChiselCylinder settings, int descriptionIndex)
{
if (descriptionIndex < 2)
return UVMatrix.Centered;
var radius = settings.topDiameterX * 0.5f;
var angle = 360.0f / settings.sides;
var sideLength = (2 * math.sin(math.radians(angle / 2.0f))) * radius;
var uv0 = UVMatrix.Identity;
uv0.U.w = ((descriptionIndex - 2) + 0.5f) * sideLength;
return uv0;
}

// The uvs of each built face, by its surface
static Dictionary<int, UVMatrix> BuiltUVs(ChiselCylinderComponent cylinder)
{
var brushes = new List<CSGTreeBrush>();
ChiselGeneratorParts.BrushesOf(cylinder, brushes);
Assert.That(brushes.Count, Is.EqualTo(1));
ref var mesh = ref BrushMeshManager.GetBrushMeshBlob(brushes[0].BrushMesh).Value;
var uvs = new Dictionary<int, UVMatrix>();
for (int p = 0; p < mesh.polygons.Length; p++)
uvs[mesh.polygons[p].descriptionIndex] = mesh.polygons[p].surface.details.UV0;
return uvs;
}

static void AssertSame(UVMatrix actual, UVMatrix expected, string what)
{
Assert.That(math.all(math.abs((float4)actual.U - (float4)expected.U) < 1e-5f) && math.all(math.abs((float4)actual.V - (float4)expected.V) < 1e-5f),
Is.True, $"{what}: {actual} instead of {expected}");
}

[Test]
public void TheGeneratedMesh_IsLaidOut()
{
var cylinder = Cylinder();
var settings = cylinder.definition.settings;
using var surfaces = BrushMeshManager.BuildInternalSurfaceArrayBlob(cylinder.SurfaceDefinition, Unity.Collections.Allocator.Temp);
var mesh = settings.GenerateMesh(surfaces, Unity.Collections.Allocator.Temp);
try
{
Assert.That(mesh.IsCreated, Is.True);
ref var polygons = ref mesh.Value.polygons;
for (int p = 0; p < polygons.Length; p++)
AssertSame(polygons[p].surface.details.UV0, Layout(settings, polygons[p].descriptionIndex), "surface " + polygons[p].descriptionIndex);
}
finally
{
mesh.Dispose();
}
}

[Test]
public void ANewCylinder_IsLaidOutAsBefore()
{
var cylinder = Cylinder();
var settings = cylinder.definition.settings;
foreach (var pair in BuiltUVs(cylinder))
AssertSame(pair.Value, Layout(settings, pair.Key), "surface " + pair.Key);
}

[Test]
public void ASurfacesOwnUVs_AreKeptWhenTheCylinderChanges()
{
var cylinder = Cylinder();
var own = UVMatrix.TRS(new Vector2(0.25f, 0), Vector3.up, 0, new Vector2(2, 2));
cylinder.SurfaceDefinition.surfaces[3].surfaceDetails.UV0 = own;
cylinder.definition.settings.height = 3;
cylinder.OnValidate();
Update();

Assert.That(cylinder.SurfaceDefinition.surfaces[3].surfaceDetails.UV0, Is.EqualTo(own), "the surface's uvs were reset");
}

// A side scaled up keeps the texture running on round the cylinder: its offset is scaled too
[Test]
public void AScaledSide_KeepsTheSidesInLine()
{
var cylinder = Cylinder();
var settings = cylinder.definition.settings;
// a new surface's uvs, scaled up
var scaled = SurfaceDetails.Default.UV0;
scaled.U = new Vector4(scaled.U.x * 2, scaled.U.y * 2, scaled.U.z * 2, scaled.U.w);
scaled.V = new Vector4(scaled.V.x * 2, scaled.V.y * 2, scaled.V.z * 2, scaled.V.w);
foreach (var surface in cylinder.SurfaceDefinition.surfaces)
surface.surfaceDetails.UV0 = scaled;
cylinder.OnValidate();
Update();

var built = BuiltUVs(cylinder);
Assert.That(built[3].U.w, Is.EqualTo(Layout(settings, 3).U.w * 2).Within(1e-5f));
Assert.That(built[4].U.w - built[3].U.w, Is.EqualTo((Layout(settings, 4).U.w - Layout(settings, 3).U.w) * 2).Within(1e-5f), "the sides don't run on");
Assert.That(built[3].U.x, Is.EqualTo(2).Within(1e-5f));
}

// A cylinder saved before held the layout in its surfaces: it is taken as untouched surfaces, not laid out twice
[Test]
public void ACylinderSavedBefore_LooksTheSame()
{
var cylinder = Cylinder();
var settings = cylinder.definition.settings;
cylinder.definition.settings.surfaceUVVersion = 0;
for (int i = 0; i < cylinder.SurfaceDefinition.surfaces.Length; i++)
cylinder.SurfaceDefinition.surfaces[i].surfaceDetails.UV0 = Layout(settings, i);
cylinder.OnValidate();
Update();

Assert.That(cylinder.definition.settings.surfaceUVVersion, Is.EqualTo(ChiselCylinder.kSurfaceUVVersion));
foreach (var pair in BuiltUVs(cylinder))
AssertSame(pair.Value, Layout(settings, pair.Key), "surface " + pair.Key);
}
}
}
2 changes: 2 additions & 0 deletions Components/Tests/ChiselCylinderUVTests.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 56 additions & 21 deletions Core/1.Input/Generators/Cylinder/ChiselCylinderDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public struct ChiselCylinder : IBrushGenerator, IChiselSurfaceNames
fitToBounds = true,
sides = 16,
smoothingGroup = 1,
type = CylinderShapeType.Cylinder
type = CylinderShapeType.Cylinder,
surfaceUVVersion = kSurfaceUVVersion
};
public static ref readonly ChiselCylinder DefaultSettings => ref kDefaultSettings;

Expand Down Expand Up @@ -77,7 +78,13 @@ public struct ChiselCylinder : IBrushGenerator, IChiselSurfaceNames

[UnityEngine.HideInInspector, AngleValue]
public float rotation;


// 1: each surface holds its own uvs, and the cylinder lays out its caps and sides on top of them when it is
// built. 0, saved before: the surfaces hold the layout itself
public const int kSurfaceUVVersion = 1;
[UnityEngine.HideInInspector]
public int surfaceUVVersion;


#region Properties
public float TopDiameterX
Expand Down Expand Up @@ -235,13 +242,23 @@ public readonly BlobAssetReference<BrushMeshBlob> GenerateMesh(BlobAssetReferenc
bottomDiameter.y = bottomDiameter.x;
}

if (!BrushMeshFactory.GenerateConicalFrustumSubMesh(topDiameter, topHeight,
if (!BrushMeshFactory.GenerateConicalFrustumSubMesh(topDiameter, topHeight,
bottomDiameter, bottomHeight,
rotation, sides, fitToBounds,
in surfaceDefinitionBlob,
out var newBrushMesh,
rotation, sides, fitToBounds,
in surfaceDefinitionBlob,
out var newBrushMesh,
allocator))// Indirect
return default;

// Each face's surface with the layout put on it (a surface mapped in world space keeps its own); the mesh is
// only hashed when it is registered, after this
ref var polygons = ref newBrushMesh.Value.polygons;
for (int p = 0; p < polygons.Length; p++)
{
ref var details = ref polygons[p].surface.details;
if ((details.detailFlags & SurfaceDetailFlags.TextureIsInWorldSpace) == 0)
details.UV0 = OnLayout(details.UV0, Layout(polygons[p].descriptionIndex));
}
return newBrushMesh;
}
#endregion
Expand All @@ -264,25 +281,43 @@ public readonly string GetSurfaceName(int index)
[BurstDiscard]
public void UpdateSurfaces(ref ChiselSurfaceArray surfaceDefinition)
{
// Top plane
surfaceDefinition.surfaces[0].surfaceDetails.UV0 = UVMatrix.Centered;
var surfaces = surfaceDefinition.surfaces;

// Bottom plane
surfaceDefinition.surfaces[1].surfaceDetails.UV0 = UVMatrix.Centered;
// Saved before, every surface held the layout (written into it again on every change): they become
// untouched surfaces, which the layout is put on when the cylinder is built, so it looks the same
if (surfaceUVVersion < kSurfaceUVVersion)
{
for (int i = 0; i < surfaces.Length; i++)
surfaces[i].surfaceDetails.UV0 = SurfaceDetails.Default.UV0;
surfaceUVVersion = kSurfaceUVVersion;
}

float radius = topDiameterX * 0.5f;
float angle = (360.0f / sides);
for (int i = 2; i < 2 + sides && i < surfaces.Length; i++)
surfaces[i].surfaceDetails.smoothingGroup = smoothingGroup;
}

// Where a surface's texture starts: the caps centered, each side after the one before it round the cylinder, so
// the texture runs on from side to side
public readonly UVMatrix Layout(int descriptionIndex)
{
if (descriptionIndex < 2)
return UVMatrix.Centered;
float radius = topDiameterX * 0.5f;
float angle = 360.0f / sides;
float sideLength = (2 * math.sin(math.radians(angle / 2.0f))) * radius;
var uv0 = UVMatrix.Identity;
uv0.U.w = ((descriptionIndex - 2) + 0.5f) * sideLength;
return uv0;
}

// Side planes
for (int i = 2; i < 2 + sides; i++)
{
var uv0 = UVMatrix.Identity;
uv0.U.w = ((i - 2) + 0.5f) * sideLength;
//uv0.V.w = 0.5f;
surfaceDefinition.surfaces[i].surfaceDetails.UV0 = uv0;
surfaceDefinition.surfaces[i].surfaceDetails.smoothingGroup = smoothingGroup;
}
// The surface's own uvs applied to the layout (its u and v are the layout's), as the sphere does with its map. They
// count from a new surface's uvs, so a surface left as it is gets the layout itself
public static UVMatrix OnLayout(UVMatrix own, UVMatrix layout)
{
var neutral = SurfaceDetails.Default.UV0;
float4 layoutU = layout.U, layoutV = layout.V, ownU = own.U, ownV = own.V;
return new UVMatrix(ownU.x * layoutU + ownU.y * layoutV + new float4(0, 0, 0, ownU.w - neutral.U.w),
ownV.x * layoutU + ownV.y * layoutV + new float4(0, 0, 0, ownV.w - neutral.V.w));
}
#endregion

Expand Down
3 changes: 3 additions & 0 deletions Editor/ComponentEditors/Base/ChiselNodeEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,10 @@ protected static void CreateAsGameObjectMenuCommand(MenuCommand menuCommand, str
component = ChiselComponentFactory.Create<T>(name);

if (component is ChiselGeneratorComponent generator)
{
ChiselActiveMaterial.GiveTo(generator);
ChiselDefaultUVScale.GiveTo(generator);
}

var gameObject = component.gameObject;
GameObjectUtility.SetParentAndAlign(gameObject, parentGameObject);
Expand Down
3 changes: 3 additions & 0 deletions Editor/SceneView/ChiselDefaultPlacementTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ public override void OnSceneGUI(SceneView sceneView, Rect dragArea)
PlacementToolDefinition.OnUpdate(ref generatedComponent.definition, height);
generatedComponent.OnValidate();
ChiselActiveMaterial.GiveTo(generatedComponent);
ChiselDefaultUVScale.GiveTo(generatedComponent);
generatedComponent.ClearHashes();
}
break;
Expand Down Expand Up @@ -249,6 +250,7 @@ public override void OnSceneGUI(SceneView sceneView, Rect dragArea)
PlacementToolDefinition.OnUpdate(ref generatedComponent.definition, bounds);
generatedComponent.OnValidate();
ChiselActiveMaterial.GiveTo(generatedComponent);
ChiselDefaultUVScale.GiveTo(generatedComponent);

Lift(generatoreModeFlags, height);
generatedComponent.ResetTreeNodes();
Expand Down Expand Up @@ -279,6 +281,7 @@ public override void OnSceneGUI(SceneView sceneView, Rect dragArea)
PlacementToolDefinition.OnUpdate(ref generatedComponent.definition, bounds);
generatedComponent.OnValidate();
ChiselActiveMaterial.GiveTo(generatedComponent);
ChiselDefaultUVScale.GiveTo(generatedComponent);

Lift(generatoreModeFlags, height);

Expand Down
1 change: 1 addition & 0 deletions Editor/SceneView/ChiselPlacementToolInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ public void Commit(ChiselGeneratorComponent generatorComponent, bool createAsBru
}

ChiselActiveMaterial.GiveTo(generatorComponent);
ChiselDefaultUVScale.GiveTo(generatorComponent);
if (createAsBrush)
{
ConvertToBrushesButton.ConvertToBrushes(generatorComponent);
Expand Down
42 changes: 42 additions & 0 deletions Editor/Settings/ChiselDefaultUVScale.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Chisel.Components;
using Chisel.Core;
using UnityEditor;
using UnityEngine;

namespace Chisel.Editors
{
// New brushes start with the UV scale set in Chisel's preferences, on every surface
public static class ChiselDefaultUVScale
{
/// <summary>Gives every surface of a new generator the default UV scale, keeping its translation and rotation. It
/// sets the scale rather than multiplying it, so giving it again changes nothing.</summary>
public static void GiveTo(ChiselGeneratorComponent generator)
{
if (!generator)
return;
var defaultScale = ChiselEditorSettings.DefaultUVScale;
if (generator.SurfaceDefinition?.surfaces == null)
generator.OnValidate();
var surfaces = generator.SurfaceDefinition?.surfaces;
if (surfaces == null)
return;
var changed = false;
foreach (var surface in surfaces)
{
if (surface == null)
continue;
var uv0 = surface.surfaceDetails.UV0;
uv0.Decompose(out var translation, out var rotation, out var scale);
if (Mathf.Approximately(scale.x, defaultScale) && Mathf.Approximately(scale.y, defaultScale))
continue;
surface.surfaceDetails.UV0 = UVMatrix.TRS(translation, uv0.PlaneNormal, rotation, new Vector2(defaultScale, defaultScale));
changed = true;
}
if (changed)
{
generator.OnValidate();
EditorUtility.SetDirty(generator);
}
}
}
}
2 changes: 2 additions & 0 deletions Editor/Settings/ChiselDefaultUVScale.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Editor/Settings/ChiselEditorSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ internal static void SetInToolBox(string toolName, bool value)
public static bool ShowMoveGizmo { get; set; } = false;
// the unit lengths are shown and typed in
public static DistanceUnit DistanceUnit { get { return Units.ActiveDistanceUnit; } set { Units.ActiveDistanceUnit = value; } }
// the UV scale new brushes start with
public static float DefaultUVScale { get; set; } = 1;

/// <summary>Which helper surfaces (collider, shadow-only, discarded, ...) are drawn in the
/// scene view on top of the regular geometry. Read by <see cref="ChiselDrawModes"/> every
Expand All @@ -64,6 +66,7 @@ public static void Load()
ShowMoveGizmo = EditorPrefs.GetBool ("ShowMoveGizmo", false);

DistanceUnit = (DistanceUnit)EditorPrefs.GetInt("DistanceUnit", (int)DistanceUnit.Meters);
DefaultUVScale = EditorPrefs.GetFloat("DefaultUVScale", 1);

HelperSurfaceFlags = (DrawModeFlags)EditorPrefs.GetInt("HelperSurfaceFlags", (int)DrawModeFlags.Default);

Expand Down Expand Up @@ -100,6 +103,7 @@ public static void Save()
EditorPrefs.SetBool("ShowMoveGizmo", ShowMoveGizmo);

EditorPrefs.SetInt ("DistanceUnit", (int)DistanceUnit);
EditorPrefs.SetFloat("DefaultUVScale", DefaultUVScale);

EditorPrefs.SetInt("HelperSurfaceFlags", (int)HelperSurfaceFlags);

Expand Down
Loading
Loading