From 7a5d1d3426019b2135ed4a351e3d04b231f615f6 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Sun, 14 Nov 2021 16:25:10 -0600 Subject: [PATCH 01/41] Uninstall docs submodule --- .gitmodules | 4 ---- docs~ | 1 - 2 files changed, 5 deletions(-) delete mode 100644 .gitmodules delete mode 160000 docs~ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 76f97e0..0000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "docs~"] - path = docs~ - url = https://github.com/zigurous/docjs - branch = com.zigurous.graphics diff --git a/docs~ b/docs~ deleted file mode 160000 index 4d87b26..0000000 --- a/docs~ +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4d87b267f5c990de54b03c55fbb259f9a7d4004e From 3c8aa069a0248acb264a0a4c7dd8db8f585930d3 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Sun, 14 Nov 2021 17:13:41 -0600 Subject: [PATCH 02/41] Add merge_branch workflow --- .github/workflows/docs.yml | 61 ------------------------------ .github/workflows/merge_branch.yml | 19 ++++++++++ 2 files changed, 19 insertions(+), 61 deletions(-) delete mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/merge_branch.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 856609e..0000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Docs - -on: - push: - branches: - - main - -jobs: - docs: - name: Docs - runs-on: windows-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - submodules: true - token: ${{ secrets.DOCS_TOKEN }} - - - name: Setup dotnet - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 3.1.x - - - name: Setup docfx - uses: crazy-max/ghaction-chocolatey@v1 - with: - args: install docfx - - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: '14' - - - name: Install dependencies - working-directory: docs~ - run: npm install - env: - NPM_TOKEN: ${{ secrets.DOCS_TOKEN }} - - - name: Generate metadata - working-directory: docs~ - run: npm run-script docfx - env: - NPM_TOKEN: ${{ secrets.DOCS_TOKEN }} - - - name: Build docs - working-directory: docs~ - continue-on-error: false - run: npm run-script build - env: - NPM_TOKEN: ${{ secrets.DOCS_TOKEN }} - - - name: Publish - if: github.event_name == 'push' - uses: peaceiris/actions-gh-pages@v3 - with: - personal_token: ${{ secrets.DOCS_TOKEN }} - external_repository: zigurous/docs - destination_dir: com.zigurous.graphics - publish_dir: docs~/public - publish_branch: main diff --git a/.github/workflows/merge_branch.yml b/.github/workflows/merge_branch.yml new file mode 100644 index 0000000..29f4689 --- /dev/null +++ b/.github/workflows/merge_branch.yml @@ -0,0 +1,19 @@ +name: Merge Branch + +on: + push: + branches: + - main + +jobs: + merge-branch: + name: Merge Branch + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: everlytic/branch-merge@1.1.2 + with: + github_token: ${{ secrets.DOCS_TOKEN }} + source_ref: 'main' + target_branch: 'docs' + commit_message_template: '[Automated] Merge {source_ref} into {target_branch}' From e30e41a80c91c8429b472191f8202fb33d11bcb8 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Thu, 13 Jan 2022 18:17:34 -0600 Subject: [PATCH 03/41] Delete FUNDING.yml --- .github/FUNDING.yml | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index e967995..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,2 +0,0 @@ -patreon: zigurous -custom: zigurous.com From 774b837d426c02c179d403ce4c5ce8c62cbdea88 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Sat, 26 Mar 2022 18:54:56 -0500 Subject: [PATCH 04/41] Use version directives to import correct package --- Runtime/AutoTile.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Runtime/AutoTile.cs b/Runtime/AutoTile.cs index 7a1eb0a..0385347 100644 --- a/Runtime/AutoTile.cs +++ b/Runtime/AutoTile.cs @@ -1,7 +1,11 @@ ο»Ώ#if UNITY_EDITOR using UnityEditor; +#if UNITY_2021_2_OR_NEWER +using UnityEditor.SceneManagement; +#else using UnityEditor.Experimental.SceneManagement; #endif +#endif using UnityEngine; namespace Zigurous.Graphics From e98cc9a82e9d655ea3085e8164f01f3116a55d30 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Thu, 31 Mar 2022 01:07:58 -0500 Subject: [PATCH 05/41] Generate docs from main branch --- .github/workflows/generate_docs.yml | 72 +++++++++++++++++++++++++++++ .github/workflows/merge_branch.yml | 19 -------- 2 files changed, 72 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/generate_docs.yml delete mode 100644 .github/workflows/merge_branch.yml diff --git a/.github/workflows/generate_docs.yml b/.github/workflows/generate_docs.yml new file mode 100644 index 0000000..8ccef88 --- /dev/null +++ b/.github/workflows/generate_docs.yml @@ -0,0 +1,72 @@ +name: Generate Docs + +on: + push: + branches: + - main + +env: + BASE_PATH: com.zigurous.graphics + PACKAGE_TITLE: "Graphics Utils" + +jobs: + generate: + name: Generate Docs + runs-on: windows-latest + steps: + - name: Checkout package + uses: actions/checkout@v3 + with: + token: ${{ secrets.DOCS_TOKEN }} + + - name: Checkout docs template + uses: actions/checkout@v3 + with: + repository: zigurous/docs-template + ref: unity-package + token: ${{ secrets.DOCS_TOKEN }} + path: docs~ + + - name: Setup dotnet + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 3.1.x + + - name: Setup docfx + uses: crazy-max/ghaction-chocolatey@v1 + with: + args: install docfx + + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: '14' + + - name: Install dependencies + working-directory: docs~ + run: npm install + env: + NPM_TOKEN: ${{ secrets.DOCS_TOKEN }} + + - name: Generate documentation + working-directory: docs~ + run: npm run-script generate $env:BASE_PATH $env:PACKAGE_TITLE + env: + NPM_TOKEN: ${{ secrets.DOCS_TOKEN }} + + - name: Build docs + working-directory: docs~ + continue-on-error: false + run: npm run-script build + env: + NPM_TOKEN: ${{ secrets.DOCS_TOKEN }} + + - name: Publish + if: github.event_name == 'push' + uses: peaceiris/actions-gh-pages@v3 + with: + personal_token: ${{ secrets.DOCS_TOKEN }} + external_repository: zigurous/docs + destination_dir: ${{ env.BASE_PATH }} + publish_dir: docs~/public + publish_branch: main diff --git a/.github/workflows/merge_branch.yml b/.github/workflows/merge_branch.yml deleted file mode 100644 index 29f4689..0000000 --- a/.github/workflows/merge_branch.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Merge Branch - -on: - push: - branches: - - main - -jobs: - merge-branch: - name: Merge Branch - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: everlytic/branch-merge@1.1.2 - with: - github_token: ${{ secrets.DOCS_TOKEN }} - source_ref: 'main' - target_branch: 'docs' - commit_message_template: '[Automated] Merge {source_ref} into {target_branch}' From c5427398188eb63d9da4d3d1a8856d17bdbdab54 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Thu, 31 Mar 2022 01:34:39 -0500 Subject: [PATCH 06/41] Add documentation manuals --- Documentation~/articles/custom-meshes.md | 61 +++++++++++++ Documentation~/articles/index.md | 25 ++++++ Documentation~/articles/installation.md | 34 +++++++ Documentation~/articles/material-tiling.md | 17 ++++ Documentation~/articles/shader-properties.md | 52 +++++++++++ Documentation~/articles/texture-drawers.md | 29 ++++++ Documentation~/data/header.json | 19 ++++ Documentation~/data/sidenav.json | 89 +++++++++++++++++++ Documentation~/images/tiling.jpg | Bin 0 -> 67108 bytes 9 files changed, 326 insertions(+) create mode 100644 Documentation~/articles/custom-meshes.md create mode 100644 Documentation~/articles/index.md create mode 100644 Documentation~/articles/installation.md create mode 100644 Documentation~/articles/material-tiling.md create mode 100644 Documentation~/articles/shader-properties.md create mode 100644 Documentation~/articles/texture-drawers.md create mode 100644 Documentation~/data/header.json create mode 100644 Documentation~/data/sidenav.json create mode 100644 Documentation~/images/tiling.jpg diff --git a/Documentation~/articles/custom-meshes.md b/Documentation~/articles/custom-meshes.md new file mode 100644 index 0000000..6b5c31a --- /dev/null +++ b/Documentation~/articles/custom-meshes.md @@ -0,0 +1,61 @@ +--- +slug: "/manual/custom-meshes" +--- + +# Custom Meshes + +The **Graphics Utils** package includes a few custom cube meshes. + +- `Cube-3.mesh`: cube mesh with 3 submeshes (one for each axis) +- `Cube-6.mesh`: cube mesh with 6 submeshes (one for each face) +- `Cube-Inverted.mesh`: cube mesh with inverted normals and triangles (inside-out) +- `Cube-Tiling.mesh` cube mesh designed specifically for [Material Tiling](/manual/material-tiling) + +There are also 3 different scripts to generate cube meshes at runtime: + +- [CubeMesh](/api/Zigurous.Graphics/CubeMesh) +- [CubeMesh3](/api/Zigurous.Graphics/CubeMesh3) +- [CubeMesh6](/api/Zigurous.Graphics/CubeMesh6) + +
+ +## β­• Inverting Meshes + +Sometimes it is useful to invert a mesh so it renders inside out. This is especially useful for cubes. Inverting a mesh flips the triangles and the normals. The **Graphics Utils** package comes with an [InvertMesh](/api/Zigurous.Graphics/InvertMesh) script that handles this automatically. + +You can also manually invert the normals and triangles of a mesh using extension methods: + +```csharp +mesh.InvertNormals(); +mesh.InvertTriangles(); + +// Returns the inverted values without changing the actual mesh +Vector3[] normals = mesh.InvertedNormals(); +int[] triangles = mesh.InvertedTriangles(); +``` + +
+ +## πŸ”° Combining Meshes + +The **Graphics Utils** package includes a script to combine multiple meshes into a single mesh. This can sometimes be used to improve rendering performance, or as a way to create custom meshes and turn them into assets. Add the [CombineChildrenMeshes](/api/Zigurous.Graphics/CombineChildrenMeshes) script to a game object that includes an empty mesh filter. The script will combine the meshes of the children objects and apply the new combined mesh to the parent mesh filter. + +You can also manually combine meshes through an extension method: + +```csharp +MeshFilter[] filters = GetComponentsInChildren(); +Mesh combinedMesh = filters.CombineMesh(); +``` + +
+ +## πŸ’Ύ Saving Meshes + +Often when generating meshes at runtime, you may want to save that mesh as an asset for future use so you do not need to regenerate them over and over. The **Graphics Utils** package comes with a [SaveMesh](/api/Zigurous.Graphics/SaveMesh) script that will save a mesh as an asset at runtime. + +You can also manually save a mesh through extension methods: + +```csharp +mesh.Save("Custom"); +meshFilter.SaveMesh("Custom"); +``` diff --git a/Documentation~/articles/index.md b/Documentation~/articles/index.md new file mode 100644 index 0000000..a823079 --- /dev/null +++ b/Documentation~/articles/index.md @@ -0,0 +1,25 @@ +--- +slug: "/manual" +--- + +# Graphics Utils + +The **Graphics Utils** package provides scripts and utilities for graphics and rendering purposes in Unity projects. The package is still early in development, and more functionality will be added over time. Let us know what other features you would like to see. + +
+ +## πŸ“Œ Overview + +- [Scripting API](/api/Zigurous.Graphics) +- [Installation](/manual/installation) +- [Changelog](/changelog) +- [License](/license) + +
+ +## πŸ“– Reference + +- [Material Tiling](/manual/material-tiling) +- [Custom Meshes](/manual/custom-meshes) +- [Shader Properties](/manual/shader-properties) +- [Texture Drawers](/manual/texture-drawers) diff --git a/Documentation~/articles/installation.md b/Documentation~/articles/installation.md new file mode 100644 index 0000000..6c998f0 --- /dev/null +++ b/Documentation~/articles/installation.md @@ -0,0 +1,34 @@ +--- +slug: "/manual/installation" +--- + +# Installation + +Use the Unity [Package Manager](https://docs.unity3d.com/Manual/upm-ui.html) to install the **Graphics Utils** package. + +1. Open the Package Manager in `Window > Package Manager` +2. Click the add (`+`) button in the status bar +3. Select `Add package from git URL` from the add menu +4. Enter the following Git URL in the text box and click Add: + +```http +https://github.com/zigurous/unity-graphics-utils.git +``` + +
+ +## 🏷️ Namespace + +Import the package namespace in each script or file you want to use it. You may need to regenerate project files/assemblies first. + +```csharp +using Zigurous.Graphics; +``` + +
+ +## πŸ’» Source Code + +The source code for the **Graphics Utils** package is in the following repository: + +- https://github.com/zigurous/unity-graphics-utils diff --git a/Documentation~/articles/material-tiling.md b/Documentation~/articles/material-tiling.md new file mode 100644 index 0000000..5a8325c --- /dev/null +++ b/Documentation~/articles/material-tiling.md @@ -0,0 +1,17 @@ +--- +slug: "/manual/material-tiling" +--- + +# Material Tiling + +One of the most powerful features included in the **Graphics Utils** package is the ability to auto tile materials based on the object's scale. In doing so, new materials are created that are unique to the object. This makes the workflow of creating materials for tiled objects effortless. Without this feature, you often end up creating dozens of variants of a material just to change the tiling values for different objects. + +Add the [AutoTile](/api/Zigurous.Graphics/AutoTile) script to the object you want to tile. The main property that is usually edited is `submeshes`. Each element in the array indicates how a submesh of the mesh is tiled, such as which axis the object is tiled on, the unit scale of the object, the texture offset, etc. For example, a plane is usually tiled around the Y+ axis, has a unit scale of 10, and only 1 submesh. + +
+ +## πŸ•‹ Cube Tiling + +When tiling cubes, the [AutoTile](/api/Zigurous.Graphics/AutoTile) script gives the best results when used with the `Cube-Tiling.mesh` asset instead of Unity's default cube mesh. This mesh asset is split into 3 separate submeshes so you can tile each axis independently from the others. The mesh also has custom UV coordinates so the materials are tiled from the center of each axis. Using the custom tiling mesh, the script should be set up with 3 submeshes tiled around the X+, Y+, and Z+ axis, respectively, and the unit scale set to 1. + + diff --git a/Documentation~/articles/shader-properties.md b/Documentation~/articles/shader-properties.md new file mode 100644 index 0000000..17366fc --- /dev/null +++ b/Documentation~/articles/shader-properties.md @@ -0,0 +1,52 @@ +--- +slug: "/manual/shader-properties" +--- + +# Shader Properties + +When setting shader properties on materials, it is more efficient to use property ids instead of strings. The **Graphics Utils** package comes with a static class [Identifier](/api/Zigurous.Graphics/Identifier) with predefined ids for common shader properties. + +```csharp +private void Start() +{ + Material material = GetComponent().material; + material.SetFloat(Identifier.Glossiness, 1f); +} +``` + +
+ +## πŸ”– Automatic Property Ids + +The [ShaderProperty](/api/Zigurous.Graphics/ShaderProperty) struct included in the **Graphics Utils** package automatically creates a property id for a given shader property name. Anywhere you might declare a variable for a custom shader property name use `ShaderProperty` instead. It will still be serialized as a string in the editor, but you can use the id when getting or setting a shader property on a material. + +```csharp +public ShaderProperty property = "_Custom"; + +private void Start() +{ + Material material = GetComponent().material; + material.SetFloat(property.id, 1f); +} +``` + +
+ +## 🌠 Animated Properties + +Sometimes you want to animate a shader property over time. The **Graphics Utils** package includes a few data structures to accomplish this. You can declare any of the following: + +- [AnimatedShaderIntProperty](/api/Zigurous.Graphics/AnimatedShaderIntProperty) +- [AnimatedShaderFloatProperty](/api/Zigurous.Graphics/AnimatedShaderFloatProperty) +- [AnimatedShaderColorProperty](/api/Zigurous.Graphics/AnimatedShaderColorProperty) + +These structs provide properties to set the animated values (usually done in the editor). At this point your script can call `Animate(material, time)` to evaluate the value at the given time and apply it to the provided material. + +```csharp +public AnimatedShaderFloatProperty property; + +private void Update() +{ + property.Animate(material, time); +} +``` diff --git a/Documentation~/articles/texture-drawers.md b/Documentation~/articles/texture-drawers.md new file mode 100644 index 0000000..93c6128 --- /dev/null +++ b/Documentation~/articles/texture-drawers.md @@ -0,0 +1,29 @@ +--- +slug: "/manual/texture-drawers" +--- + +# Texture Drawers + +The **Graphics Utils** package includes a base class for drawing textures at runtime. It provides the boilerplate code for creating and drawing new textures programmatically. Create a new class that inherits from [TextureDrawer](/api/Zigurous.Graphics/TextureDrawer) and override the function `SetPixels(Texture2D)` to complete the implementation. + +```csharp +public class CustomTextureDrawer : TextureDrawer +{ + public override void SetPixels(Texture2D texture) + { + // Handle setting the pixel colors here... + } +} +``` + +
+ +## πŸ–ΌοΈ Rendering + +The [TextureDrawer](/api/Zigurous.Graphics/TextureDrawer) script will automatically apply your texture to the renderer's main material on the object if a Renderer component is available. This is not required, but often is useful. There are additional customization options available in regards to the rendering, such as which shader property the texture is set to. + +
+ +## 🏁 Checkerboard + +The **Graphics Utils** package includes a script [CheckerboardTextureDrawer](/api/Zigurous.Graphics/CheckerboardTextureDrawer) as a sample implementation. It is, of course, a fully functional script that draws a checkerboard pattern. This can be used however you desire, and there are even a number of customization options available. diff --git a/Documentation~/data/header.json b/Documentation~/data/header.json new file mode 100644 index 0000000..0284048 --- /dev/null +++ b/Documentation~/data/header.json @@ -0,0 +1,19 @@ +[ + { + "name": "Manual", + "basePath": "/manual" + }, + { + "name": "Scripting API", + "basePath": "/api", + "api": true + }, + { + "name": "Changelog", + "basePath": "/changelog" + }, + { + "name": "License", + "basePath": "/license" + } +] diff --git a/Documentation~/data/sidenav.json b/Documentation~/data/sidenav.json new file mode 100644 index 0000000..ed9b08b --- /dev/null +++ b/Documentation~/data/sidenav.json @@ -0,0 +1,89 @@ +[ + { + "basePath": "/", + "categories": [ + { + "title": "πŸ“Œ Overview", + "items": [ + { + "name": "Getting Started", + "path": "/manual" + }, + { + "name": "Installation", + "path": "/manual/installation" + }, + { + "name": "Changelog", + "path": "/changelog" + }, + { + "name": "License", + "path": "/license" + } + ] + }, + { + "title": "πŸ“– Reference", + "items": [ + { + "name": "Material Tiling", + "path": "/manual/material-tiling" + }, + { + "name": "Custom Meshes", + "path": "/manual/custom-meshes" + }, + { + "name": "Shader Properties", + "path": "/manual/shader-properties" + }, + { + "name": "Texture Drawers", + "path": "/manual/texture-drawers" + } + ] + }, + { + "title": "πŸ’¬ Contact", + "items": [ + { + "name": "Discord", + "href": "https://discord.gg/DdYyWVb", + "icon": "launch" + }, + { + "name": "Twitter", + "href": "https://twitter.com/zigurous", + "icon": "launch" + } + ] + }, + { + "title": "πŸ”— Other Links", + "items": [ + { + "name": "GitHub", + "href": "https://github.com/zigurous/unity-graphics-utils", + "icon": "launch" + }, + { + "name": "Asset Store", + "href": "https://assetstore.unity.com/publishers/51884?preview=1", + "icon": "launch" + }, + { + "name": "YouTube", + "href": "https://youtube.com/c/zigurous?sub_confirmation=1", + "icon": "launch" + }, + { + "name": "Patreon", + "href": "https://patreon.com/zigurous", + "icon": "launch" + } + ] + } + ] + } +] diff --git a/Documentation~/images/tiling.jpg b/Documentation~/images/tiling.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f7f58c48bc1af40b78028042f14905525137aa43 GIT binary patch literal 67108 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 A$^ZZW literal 0 HcmV?d00001 From fe1c8715ed157855df3ce1c7626c4105a417f937 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Thu, 31 Mar 2022 02:07:30 -0500 Subject: [PATCH 07/41] Update asset store link --- Documentation~/data/sidenav.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation~/data/sidenav.json b/Documentation~/data/sidenav.json index ed9b08b..a401ffc 100644 --- a/Documentation~/data/sidenav.json +++ b/Documentation~/data/sidenav.json @@ -69,7 +69,7 @@ }, { "name": "Asset Store", - "href": "https://assetstore.unity.com/publishers/51884?preview=1", + "href": "https://assetstore.unity.com/publishers/51884", "icon": "launch" }, { From 5102760103bee98afdc0c655a874891e8696a074 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Thu, 31 Mar 2022 02:12:38 -0500 Subject: [PATCH 08/41] Change SetPixels to public function --- Runtime/CheckerboardTextureDrawer.cs | 2 +- Runtime/TextureDrawer.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Runtime/CheckerboardTextureDrawer.cs b/Runtime/CheckerboardTextureDrawer.cs index 5ab26b3..96eb1f8 100644 --- a/Runtime/CheckerboardTextureDrawer.cs +++ b/Runtime/CheckerboardTextureDrawer.cs @@ -62,7 +62,7 @@ public CheckerboardSettings(int rows, int columns, Color colorA, Color colorB) public CheckerboardSettings checkerboard = new CheckerboardSettings(4, 4, Color.white, Color.black); /// - protected override void SetPixels(Texture2D texture) + public override void SetPixels(Texture2D texture) { int rectWidth = texture.width / checkerboard.columns; int rectHeight = texture.height / checkerboard.rows; diff --git a/Runtime/TextureDrawer.cs b/Runtime/TextureDrawer.cs index cdaae78..5149edb 100644 --- a/Runtime/TextureDrawer.cs +++ b/Runtime/TextureDrawer.cs @@ -232,7 +232,7 @@ private void SetTransformScale() /// Sets the pixels of the texture. /// /// The texture to set the pixels on. - protected abstract void SetPixels(Texture2D texture); + public abstract void SetPixels(Texture2D texture); } From 56ae2fb827ca6a9db6231597d4ec1309dbb25328 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Thu, 31 Mar 2022 02:15:56 -0500 Subject: [PATCH 09/41] Add documentation comments to Unity lifecycle methods --- Runtime/CheckerboardTextureDrawer.cs | 1 + Runtime/TextureDrawer.cs | 23 ++++++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Runtime/CheckerboardTextureDrawer.cs b/Runtime/CheckerboardTextureDrawer.cs index 96eb1f8..ffe378a 100644 --- a/Runtime/CheckerboardTextureDrawer.cs +++ b/Runtime/CheckerboardTextureDrawer.cs @@ -87,6 +87,7 @@ public override void SetPixels(Texture2D texture) } } + /// protected override void OnValidate() { base.OnValidate(); diff --git a/Runtime/TextureDrawer.cs b/Runtime/TextureDrawer.cs index 5149edb..a49238a 100644 --- a/Runtime/TextureDrawer.cs +++ b/Runtime/TextureDrawer.cs @@ -127,11 +127,27 @@ public RenderSettings(ShaderProperty shaderTextureName, float scaleFactor = 1f, /// public bool invalidated { get; internal set; } + ///

+ /// Unity lifecycle method to handle initialization. + /// protected virtual void Awake() { renderer = GetComponent(); } + /// + /// Unity lifecycle method to handle the behavior being enabled. + /// + protected virtual void OnEnable() + { + if (Application.isPlaying) { + Draw(); + } + } + + /// + /// Unity lifecycle method to handle editor validation. + /// protected virtual void OnValidate() { invalidated = true; @@ -149,13 +165,6 @@ protected virtual void OnValidate() } } - protected virtual void OnEnable() - { - if (Application.isPlaying) { - Draw(); - } - } - private void Update() { #if UNITY_EDITOR From d981935c934810b4556381ec786f76a11f062bb6 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 4 Apr 2022 04:02:11 -0500 Subject: [PATCH 10/41] Generate docs on workflow_dispatch --- .github/workflows/generate_docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate_docs.yml b/.github/workflows/generate_docs.yml index 8ccef88..6d0448b 100644 --- a/.github/workflows/generate_docs.yml +++ b/.github/workflows/generate_docs.yml @@ -4,6 +4,7 @@ on: push: branches: - main + workflow_dispatch: env: BASE_PATH: com.zigurous.graphics @@ -62,7 +63,6 @@ jobs: NPM_TOKEN: ${{ secrets.DOCS_TOKEN }} - name: Publish - if: github.event_name == 'push' uses: peaceiris/actions-gh-pages@v3 with: personal_token: ${{ secrets.DOCS_TOKEN }} From 9264e349c8883f52d84622541ae1c2c0db804340 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 4 Apr 2022 04:28:48 -0500 Subject: [PATCH 11/41] Update docs navigation --- Documentation~/articles/installation.md | 2 +- Documentation~/data/header.json | 9 +- Documentation~/data/sidenav.json | 147 ++++++++++++------------ 3 files changed, 76 insertions(+), 82 deletions(-) diff --git a/Documentation~/articles/installation.md b/Documentation~/articles/installation.md index 6c998f0..f4a7db6 100644 --- a/Documentation~/articles/installation.md +++ b/Documentation~/articles/installation.md @@ -1,5 +1,5 @@ --- -slug: "/manual/installation" +slug: "/installation" --- # Installation diff --git a/Documentation~/data/header.json b/Documentation~/data/header.json index 0284048..a2cc351 100644 --- a/Documentation~/data/header.json +++ b/Documentation~/data/header.json @@ -1,19 +1,18 @@ [ { "name": "Manual", - "basePath": "/manual" + "path": "/manual" }, { "name": "Scripting API", - "basePath": "/api", - "api": true + "path": "/api" }, { "name": "Changelog", - "basePath": "/changelog" + "path": "/changelog" }, { "name": "License", - "basePath": "/license" + "path": "/license" } ] diff --git a/Documentation~/data/sidenav.json b/Documentation~/data/sidenav.json index a401ffc..4862a52 100644 --- a/Documentation~/data/sidenav.json +++ b/Documentation~/data/sidenav.json @@ -1,88 +1,83 @@ [ { - "basePath": "/", - "categories": [ + "title": "πŸ“Œ Overview", + "items": [ { - "title": "πŸ“Œ Overview", - "items": [ - { - "name": "Getting Started", - "path": "/manual" - }, - { - "name": "Installation", - "path": "/manual/installation" - }, - { - "name": "Changelog", - "path": "/changelog" - }, - { - "name": "License", - "path": "/license" - } - ] + "name": "Getting Started", + "path": "/manual" }, { - "title": "πŸ“– Reference", - "items": [ - { - "name": "Material Tiling", - "path": "/manual/material-tiling" - }, - { - "name": "Custom Meshes", - "path": "/manual/custom-meshes" - }, - { - "name": "Shader Properties", - "path": "/manual/shader-properties" - }, - { - "name": "Texture Drawers", - "path": "/manual/texture-drawers" - } - ] + "name": "Installation", + "path": "/installation" }, { - "title": "πŸ’¬ Contact", - "items": [ - { - "name": "Discord", - "href": "https://discord.gg/DdYyWVb", - "icon": "launch" - }, - { - "name": "Twitter", - "href": "https://twitter.com/zigurous", - "icon": "launch" - } - ] + "name": "Changelog", + "path": "/changelog" }, { - "title": "πŸ”— Other Links", - "items": [ - { - "name": "GitHub", - "href": "https://github.com/zigurous/unity-graphics-utils", - "icon": "launch" - }, - { - "name": "Asset Store", - "href": "https://assetstore.unity.com/publishers/51884", - "icon": "launch" - }, - { - "name": "YouTube", - "href": "https://youtube.com/c/zigurous?sub_confirmation=1", - "icon": "launch" - }, - { - "name": "Patreon", - "href": "https://patreon.com/zigurous", - "icon": "launch" - } - ] + "name": "License", + "path": "/license" + } + ] + }, + { + "title": "πŸ“– Reference", + "items": [ + { + "name": "Material Tiling", + "path": "/manual/material-tiling" + }, + { + "name": "Custom Meshes", + "path": "/manual/custom-meshes" + }, + { + "name": "Shader Properties", + "path": "/manual/shader-properties" + }, + { + "name": "Texture Drawers", + "path": "/manual/texture-drawers" + } + ] + }, + { + "title": "πŸ’¬ Contact", + "items": [ + { + "name": "Discord", + "href": "https://discord.gg/DdYyWVb", + "icon": "launch" + }, + { + "name": "Twitter", + "href": "https://twitter.com/zigurous", + "icon": "launch" + } + ] + }, + { + "title": "πŸ”— Other Links", + "items": [ + { + "name": "GitHub", + "href": "https://github.com/zigurous/unity-graphics-utils", + "icon": "launch" + }, + { + "name": "Asset Store", + "href": "https://assetstore.unity.com/publishers/51884", + "icon": "launch" + }, + { + "name": "YouTube", + "href": "https://youtube.com/c/zigurous?sub_confirmation=1", + "icon": "launch" + }, + { + "name": "Patreon", + "href": "https://patreon.com/zigurous", + "icon": "launch" } ] } From 72eb86307f35770e059041c2c162db13475544e4 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 4 Apr 2022 15:59:12 -0500 Subject: [PATCH 12/41] Separate build and publish into two jobs --- .github/workflows/generate_docs.yml | 33 +++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/.github/workflows/generate_docs.yml b/.github/workflows/generate_docs.yml index 6d0448b..01e5103 100644 --- a/.github/workflows/generate_docs.yml +++ b/.github/workflows/generate_docs.yml @@ -11,8 +11,8 @@ env: PACKAGE_TITLE: "Graphics Utils" jobs: - generate: - name: Generate Docs + build: + name: Build runs-on: windows-latest steps: - name: Checkout package @@ -39,34 +39,53 @@ jobs: args: install docfx - name: Setup node - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: '14' + cache: 'yarn' + cache-dependency-path: docs~/yarn.lock - name: Install dependencies working-directory: docs~ - run: npm install + run: yarn install env: NPM_TOKEN: ${{ secrets.DOCS_TOKEN }} - name: Generate documentation working-directory: docs~ - run: npm run-script generate $env:BASE_PATH $env:PACKAGE_TITLE + run: yarn generate $env:BASE_PATH $env:PACKAGE_TITLE env: NPM_TOKEN: ${{ secrets.DOCS_TOKEN }} - name: Build docs working-directory: docs~ continue-on-error: false - run: npm run-script build + run: yarn build env: NPM_TOKEN: ${{ secrets.DOCS_TOKEN }} + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: docs + path: docs~/public + + publish: + name: Publish + needs: build + runs-on: windows-latest + steps: + - name: Download artifact + uses: actions/download-artifact@v3 + with: + name: docs + path: docs + - name: Publish uses: peaceiris/actions-gh-pages@v3 with: personal_token: ${{ secrets.DOCS_TOKEN }} external_repository: zigurous/docs destination_dir: ${{ env.BASE_PATH }} - publish_dir: docs~/public + publish_dir: docs publish_branch: main From a20c11da274cdb98e93d71d107283c4cb5bb7519 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 4 Apr 2022 16:04:44 -0500 Subject: [PATCH 13/41] Remove http syntax highlighting for git url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 58191e9..fb31b3a 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Use the Unity [Package Manager](https://docs.unity3d.com/Manual/upm-ui.html) to 3. Select `Add package from git URL` from the add menu 4. Enter the following Git URL in the text box and click Add: -```http +``` https://github.com/zigurous/unity-graphics-utils.git ``` From 851000ec443208969f014a96a08011554c5a692e Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 4 Apr 2022 23:43:07 -0500 Subject: [PATCH 14/41] Dispatch workflow to publish docs --- .github/workflows/generate_docs.yml | 33 ++++++++++++++++------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/.github/workflows/generate_docs.yml b/.github/workflows/generate_docs.yml index 01e5103..31ccae4 100644 --- a/.github/workflows/generate_docs.yml +++ b/.github/workflows/generate_docs.yml @@ -41,8 +41,8 @@ jobs: - name: Setup node uses: actions/setup-node@v3 with: - node-version: '14' - cache: 'yarn' + node-version: 14 + cache: yarn cache-dependency-path: docs~/yarn.lock - name: Install dependencies @@ -75,17 +75,20 @@ jobs: needs: build runs-on: windows-latest steps: - - name: Download artifact - uses: actions/download-artifact@v3 + - name: Dispatch workflow + uses: actions/github-script@v6 with: - name: docs - path: docs - - - name: Publish - uses: peaceiris/actions-gh-pages@v3 - with: - personal_token: ${{ secrets.DOCS_TOKEN }} - external_repository: zigurous/docs - destination_dir: ${{ env.BASE_PATH }} - publish_dir: docs - publish_branch: main + github-token: ${{ secrets.DOCS_TOKEN }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: 'zigurous', + repo: 'docs', + ref: 'main', + workflow_id: 'publish.yml', + inputs: { + path: process.env.BASE_PATH, + repo: process.env.GITHUB_REPOSITORY, + commit: context.sha, + workflow: 'generate_docs.yml' + } + }) From d716ff84c68fd5e8d0b052f507dfda924cbf03aa Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 4 Apr 2022 23:55:46 -0500 Subject: [PATCH 15/41] Configure concurrency to cancel builds in-progress --- .github/workflows/generate_docs.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/generate_docs.yml b/.github/workflows/generate_docs.yml index 31ccae4..0334d1c 100644 --- a/.github/workflows/generate_docs.yml +++ b/.github/workflows/generate_docs.yml @@ -6,6 +6,10 @@ on: - main workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: BASE_PATH: com.zigurous.graphics PACKAGE_TITLE: "Graphics Utils" From ef17e9adc0209dc43498d67d7de3f0f77b5863f5 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Tue, 5 Apr 2022 01:40:20 -0500 Subject: [PATCH 16/41] Use new publish_artifact workflow --- .github/workflows/generate_docs.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/generate_docs.yml b/.github/workflows/generate_docs.yml index 0334d1c..86b56ba 100644 --- a/.github/workflows/generate_docs.yml +++ b/.github/workflows/generate_docs.yml @@ -88,11 +88,12 @@ jobs: owner: 'zigurous', repo: 'docs', ref: 'main', - workflow_id: 'publish.yml', + workflow_id: 'publish_artifact.yml', inputs: { - path: process.env.BASE_PATH, - repo: process.env.GITHUB_REPOSITORY, - commit: context.sha, - workflow: 'generate_docs.yml' + destination_path: process.env.BASE_PATH, + artifact_repo: process.env.GITHUB_REPOSITORY, + artifact_commit: process.env.GITHUB_SHA, + artifact_workflow: 'generate_docs.yml', + artifact_name: 'docs' } }) From e1b859787de3a73d72f8287f929b42d729785f7b Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Sun, 1 May 2022 19:01:12 -0500 Subject: [PATCH 17/41] Update documentation comments --- Runtime/TextureDrawer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Runtime/TextureDrawer.cs b/Runtime/TextureDrawer.cs index a49238a..58fd1fd 100644 --- a/Runtime/TextureDrawer.cs +++ b/Runtime/TextureDrawer.cs @@ -128,7 +128,7 @@ public RenderSettings(ShaderProperty shaderTextureName, float scaleFactor = 1f, public bool invalidated { get; internal set; } /// - /// Unity lifecycle method to handle initialization. + /// A Unity lifecycle method called when the behavior is initialized. /// protected virtual void Awake() { @@ -136,7 +136,7 @@ protected virtual void Awake() } /// - /// Unity lifecycle method to handle the behavior being enabled. + /// A Unity lifecycle method called when the behavior is enabled. /// protected virtual void OnEnable() { @@ -146,7 +146,7 @@ protected virtual void OnEnable() } /// - /// Unity lifecycle method to handle editor validation. + /// A Unity lifecycle method called during editor validation. /// protected virtual void OnValidate() { From ca821b28b18af14529d10498fc6475b8d48d8c0b Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Thu, 19 May 2022 23:07:31 -0500 Subject: [PATCH 18/41] Reformat docs index page --- Documentation~/articles/index.md | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/Documentation~/articles/index.md b/Documentation~/articles/index.md index a823079..f0e5026 100644 --- a/Documentation~/articles/index.md +++ b/Documentation~/articles/index.md @@ -8,18 +8,24 @@ The **Graphics Utils** package provides scripts and utilities for graphics and r
-## πŸ“Œ Overview +## Overview -- [Scripting API](/api/Zigurous.Graphics) -- [Installation](/manual/installation) -- [Changelog](/changelog) -- [License](/license) +#### βš™οΈ [Installation](/installation) + +#### 🧰 [Scripting API](/api/Zigurous.Graphics) + +#### πŸ“‹ [Changelog](/changelog) + +#### βš–οΈ [License](/license)
-## πŸ“– Reference +## Reference + +#### πŸ›€οΈ [Material Tiling](/manual/material-tiling) + +#### πŸ”° [Custom Meshes](/manual/custom-meshes) + +#### 🌌 [Shader Properties](/manual/shader-properties) -- [Material Tiling](/manual/material-tiling) -- [Custom Meshes](/manual/custom-meshes) -- [Shader Properties](/manual/shader-properties) -- [Texture Drawers](/manual/texture-drawers) +#### πŸ–ΌοΈ [Texture Drawers](/manual/texture-drawers) From 4df4815f30fda69f3f52c6687c0954fdf1fd2e68 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Fri, 10 Jun 2022 14:16:33 -0500 Subject: [PATCH 19/41] Update workflow to use composite action --- .github/workflows/generate_docs.yml | 41 +++++------------------------ 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/.github/workflows/generate_docs.yml b/.github/workflows/generate_docs.yml index 86b56ba..12baf60 100644 --- a/.github/workflows/generate_docs.yml +++ b/.github/workflows/generate_docs.yml @@ -11,7 +11,7 @@ concurrency: cancel-in-progress: true env: - BASE_PATH: com.zigurous.graphics + PACKAGE_BASE_PATH: com.zigurous.graphics PACKAGE_TITLE: "Graphics Utils" jobs: @@ -32,41 +32,12 @@ jobs: token: ${{ secrets.DOCS_TOKEN }} path: docs~ - - name: Setup dotnet - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 3.1.x - - - name: Setup docfx - uses: crazy-max/ghaction-chocolatey@v1 - with: - args: install docfx - - - name: Setup node - uses: actions/setup-node@v3 - with: - node-version: 14 - cache: yarn - cache-dependency-path: docs~/yarn.lock - - - name: Install dependencies - working-directory: docs~ - run: yarn install - env: - NPM_TOKEN: ${{ secrets.DOCS_TOKEN }} - - - name: Generate documentation - working-directory: docs~ - run: yarn generate $env:BASE_PATH $env:PACKAGE_TITLE - env: - NPM_TOKEN: ${{ secrets.DOCS_TOKEN }} - - name: Build docs - working-directory: docs~ - continue-on-error: false - run: yarn build - env: - NPM_TOKEN: ${{ secrets.DOCS_TOKEN }} + uses: ./docs~/.github/actions/build-docs + with: + npm_token: ${{ secrets.DOCS_TOKEN }} + package_base_path: ${{ env.PACKAGE_BASE_PATH }} + package_title: ${{ env.PACKAGE_TITLE }} - name: Upload artifact uses: actions/upload-artifact@v3 From 18c08dbfbb567b8ad862673053427b8040d0244e Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Fri, 10 Jun 2022 14:21:59 -0500 Subject: [PATCH 20/41] Change BASE_PATH to PACKAGE_BASE_PATH --- .github/workflows/generate_docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate_docs.yml b/.github/workflows/generate_docs.yml index 12baf60..a321e91 100644 --- a/.github/workflows/generate_docs.yml +++ b/.github/workflows/generate_docs.yml @@ -61,7 +61,7 @@ jobs: ref: 'main', workflow_id: 'publish_artifact.yml', inputs: { - destination_path: process.env.BASE_PATH, + destination_path: process.env.PACKAGE_BASE_PATH, artifact_repo: process.env.GITHUB_REPOSITORY, artifact_commit: process.env.GITHUB_SHA, artifact_workflow: 'generate_docs.yml', From d34db623d41510d74efbb324e6611ff61de72d81 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Fri, 10 Jun 2022 16:52:37 -0500 Subject: [PATCH 21/41] Use reusable workflow to generate docs --- .github/workflows/generate-docs.yml | 23 ++++++++++ .github/workflows/generate_docs.yml | 70 ----------------------------- 2 files changed, 23 insertions(+), 70 deletions(-) create mode 100644 .github/workflows/generate-docs.yml delete mode 100644 .github/workflows/generate_docs.yml diff --git a/.github/workflows/generate-docs.yml b/.github/workflows/generate-docs.yml new file mode 100644 index 0000000..983e0ab --- /dev/null +++ b/.github/workflows/generate-docs.yml @@ -0,0 +1,23 @@ +name: Generate Docs + +on: + push: + branches: + - main + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + generate: + name: Docs + uses: zigurous/docs/.github/workflows/unity-package.yml@main + with: + package_title: "Graphics Utils" + package_base_path: com.zigurous.graphics + package_workflow: generate-docs.yml + package_artifact: docs + secrets: + token: ${{ secrets.DOCS_TOKEN }} diff --git a/.github/workflows/generate_docs.yml b/.github/workflows/generate_docs.yml deleted file mode 100644 index a321e91..0000000 --- a/.github/workflows/generate_docs.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Generate Docs - -on: - push: - branches: - - main - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - PACKAGE_BASE_PATH: com.zigurous.graphics - PACKAGE_TITLE: "Graphics Utils" - -jobs: - build: - name: Build - runs-on: windows-latest - steps: - - name: Checkout package - uses: actions/checkout@v3 - with: - token: ${{ secrets.DOCS_TOKEN }} - - - name: Checkout docs template - uses: actions/checkout@v3 - with: - repository: zigurous/docs-template - ref: unity-package - token: ${{ secrets.DOCS_TOKEN }} - path: docs~ - - - name: Build docs - uses: ./docs~/.github/actions/build-docs - with: - npm_token: ${{ secrets.DOCS_TOKEN }} - package_base_path: ${{ env.PACKAGE_BASE_PATH }} - package_title: ${{ env.PACKAGE_TITLE }} - - - name: Upload artifact - uses: actions/upload-artifact@v3 - with: - name: docs - path: docs~/public - - publish: - name: Publish - needs: build - runs-on: windows-latest - steps: - - name: Dispatch workflow - uses: actions/github-script@v6 - with: - github-token: ${{ secrets.DOCS_TOKEN }} - script: | - await github.rest.actions.createWorkflowDispatch({ - owner: 'zigurous', - repo: 'docs', - ref: 'main', - workflow_id: 'publish_artifact.yml', - inputs: { - destination_path: process.env.PACKAGE_BASE_PATH, - artifact_repo: process.env.GITHUB_REPOSITORY, - artifact_commit: process.env.GITHUB_SHA, - artifact_workflow: 'generate_docs.yml', - artifact_name: 'docs' - } - }) From e9db5fd2b6eba1565b4466e7f4e9891f04bca9be Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 14:48:08 -0500 Subject: [PATCH 22/41] Add HiddenMaterialPropertyDrawer --- Editor/HiddenMaterialPropertyDrawer.cs | 17 +++++++++++++++++ Editor/HiddenMaterialPropertyDrawer.cs.meta | 11 +++++++++++ 2 files changed, 28 insertions(+) create mode 100644 Editor/HiddenMaterialPropertyDrawer.cs create mode 100644 Editor/HiddenMaterialPropertyDrawer.cs.meta diff --git a/Editor/HiddenMaterialPropertyDrawer.cs b/Editor/HiddenMaterialPropertyDrawer.cs new file mode 100644 index 0000000..f6bb9c6 --- /dev/null +++ b/Editor/HiddenMaterialPropertyDrawer.cs @@ -0,0 +1,17 @@ +ο»Ώusing UnityEditor; +using UnityEngine; + +namespace Zigurous.Graphics.Editor +{ + public sealed class HiddenMaterialPropertyDrawer : MaterialPropertyDrawer + { + public override void OnGUI(Rect position, MaterialProperty prop, string label, MaterialEditor editor) {} + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + return -EditorGUIUtility.standardVerticalSpacing; + } + + } + +} diff --git a/Editor/HiddenMaterialPropertyDrawer.cs.meta b/Editor/HiddenMaterialPropertyDrawer.cs.meta new file mode 100644 index 0000000..83582e5 --- /dev/null +++ b/Editor/HiddenMaterialPropertyDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7487b70c78507b64fa1642a570f69200 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 7b8d90ffe5eda909e081755e709e3404bf70faaa Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 14:48:08 -0500 Subject: [PATCH 23/41] Set help urls --- Runtime/AutoTile.cs | 3 ++- Runtime/CheckerboardTextureDrawer.cs | 1 + Runtime/CombineChildrenMeshes.cs | 3 ++- Runtime/CubeMesh.cs | 3 ++- Runtime/CubeMesh3.cs | 3 ++- Runtime/CubeMesh6.cs | 3 ++- Runtime/InvertMesh.cs | 3 ++- Runtime/SaveMesh.cs | 3 ++- 8 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Runtime/AutoTile.cs b/Runtime/AutoTile.cs index 0385347..9041bc4 100644 --- a/Runtime/AutoTile.cs +++ b/Runtime/AutoTile.cs @@ -14,8 +14,9 @@ namespace Zigurous.Graphics /// Automatically tiles the material textures based on the object's scale. /// [ExecuteAlways] - [RequireComponent(typeof(Renderer))] [AddComponentMenu("Zigurous/Graphics/Auto Tile")] + [HelpURL("https://docs.zigurous.com/com.zigurous.graphics/api/Zigurous.Graphics/AutoTile")] + [RequireComponent(typeof(Renderer))] public sealed class AutoTile : MonoBehaviour { /// diff --git a/Runtime/CheckerboardTextureDrawer.cs b/Runtime/CheckerboardTextureDrawer.cs index ffe378a..6062846 100644 --- a/Runtime/CheckerboardTextureDrawer.cs +++ b/Runtime/CheckerboardTextureDrawer.cs @@ -6,6 +6,7 @@ namespace Zigurous.Graphics /// Draws a texture of a checkerboard pattern. /// [AddComponentMenu("Zigurous/Graphics/Checkerboard Texture Drawer")] + [HelpURL("https://docs.zigurous.com/com.zigurous.graphics/api/Zigurous.Graphics/CheckerboardTextureDrawer")] public sealed class CheckerboardTextureDrawer : TextureDrawer { /// diff --git a/Runtime/CombineChildrenMeshes.cs b/Runtime/CombineChildrenMeshes.cs index 33d72c1..a11d7e5 100644 --- a/Runtime/CombineChildrenMeshes.cs +++ b/Runtime/CombineChildrenMeshes.cs @@ -5,9 +5,10 @@ namespace Zigurous.Graphics /// /// Combines the meshes of the children of the game object into one mesh. /// + [AddComponentMenu("Zigurous/Graphics/Combine Children Meshes")] + [HelpURL("https://docs.zigurous.com/com.zigurous.graphics/api/Zigurous.Graphics/CombineChildrenMeshes")] [RequireComponent(typeof(MeshFilter))] [RequireComponent(typeof(MeshRenderer))] - [AddComponentMenu("Zigurous/Graphics/Combine Children Meshes")] public sealed class CombineChildrenMeshes : MonoBehaviour { /// diff --git a/Runtime/CubeMesh.cs b/Runtime/CubeMesh.cs index 8f97076..fb958f8 100644 --- a/Runtime/CubeMesh.cs +++ b/Runtime/CubeMesh.cs @@ -6,8 +6,9 @@ namespace Zigurous.Graphics /// Generates a new cube mesh and applies it to the mesh filter. /// [ExecuteAlways] - [RequireComponent(typeof(MeshFilter))] [AddComponentMenu("Zigurous/Graphics/Cube Mesh")] + [HelpURL("https://docs.zigurous.com/com.zigurous.graphics/api/Zigurous.Graphics/CubeMesh")] + [RequireComponent(typeof(MeshFilter))] public sealed class CubeMesh : MonoBehaviour { private static Mesh m_SharedMesh; diff --git a/Runtime/CubeMesh3.cs b/Runtime/CubeMesh3.cs index 7b533cb..8c02acc 100644 --- a/Runtime/CubeMesh3.cs +++ b/Runtime/CubeMesh3.cs @@ -7,8 +7,9 @@ namespace Zigurous.Graphics /// applies it to the mesh filter. /// [ExecuteAlways] - [RequireComponent(typeof(MeshFilter))] [AddComponentMenu("Zigurous/Graphics/Cube Mesh 3")] + [HelpURL("https://docs.zigurous.com/com.zigurous.graphics/api/Zigurous.Graphics/CubeMesh3")] + [RequireComponent(typeof(MeshFilter))] public sealed class CubeMesh3 : MonoBehaviour { /// diff --git a/Runtime/CubeMesh6.cs b/Runtime/CubeMesh6.cs index 96286c0..3625ca4 100644 --- a/Runtime/CubeMesh6.cs +++ b/Runtime/CubeMesh6.cs @@ -7,8 +7,9 @@ namespace Zigurous.Graphics /// applies it to the mesh filter. /// [ExecuteAlways] - [RequireComponent(typeof(MeshFilter))] [AddComponentMenu("Zigurous/Graphics/Cube Mesh 6")] + [HelpURL("https://docs.zigurous.com/com.zigurous.graphics/api/Zigurous.Graphics/CubeMesh6")] + [RequireComponent(typeof(MeshFilter))] public sealed class CubeMesh6 : MonoBehaviour { /// diff --git a/Runtime/InvertMesh.cs b/Runtime/InvertMesh.cs index 9644fc3..122e192 100644 --- a/Runtime/InvertMesh.cs +++ b/Runtime/InvertMesh.cs @@ -5,8 +5,9 @@ namespace Zigurous.Graphics /// /// Inverts the normals and triangles of the mesh so it renders inside-out. /// - [RequireComponent(typeof(MeshFilter))] [AddComponentMenu("Zigurous/Graphics/Invert Mesh")] + [HelpURL("https://docs.zigurous.com/com.zigurous.graphics/api/Zigurous.Graphics/InvertMesh")] + [RequireComponent(typeof(MeshFilter))] public sealed class InvertMesh : MonoBehaviour { /// diff --git a/Runtime/SaveMesh.cs b/Runtime/SaveMesh.cs index 44dd6ad..103a275 100644 --- a/Runtime/SaveMesh.cs +++ b/Runtime/SaveMesh.cs @@ -5,8 +5,9 @@ namespace Zigurous.Graphics /// /// Saves the mesh of a mesh filter into a project asset. /// - [RequireComponent(typeof(MeshFilter))] [AddComponentMenu("Zigurous/Graphics/Save Mesh")] + [HelpURL("https://docs.zigurous.com/com.zigurous.graphics/api/Zigurous.Graphics/SaveMesh")] + [RequireComponent(typeof(MeshFilter))] public sealed class SaveMesh : MonoBehaviour { /// From b931a729a56ab0fb0dad50b9ac0a5ee816d01ec1 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 14:48:08 -0500 Subject: [PATCH 24/41] Update package version to 0.4.0 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72bb985..d136bd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.4.0] - 2022/10/30 + +### Added + +- New `HiddenMaterialPropertyDrawer` attribute to hide material properties +- Help URLs added to behaviors + ## [0.3.0] - 2021/11/14 ### Added diff --git a/package.json b/package.json index 2ad8bfb..61c6b9c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.zigurous.graphics", - "version": "0.3.0", + "version": "0.4.0", "displayName": "Graphics Utils", "description": "The Graphics Utils package provides scripts and utilities for graphics and rendering purposes in Unity projects.", "unity": "2019.4", From ace9f54c0fa0bf33bb2d7944d671d9f04f0e0c0c Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 14:31:32 -0500 Subject: [PATCH 25/41] Delete ShaderProperty and AnimatedShaderProperty --- Editor/ShaderPropertyPropertyDrawer.cs | 30 ---- Editor/ShaderPropertyPropertyDrawer.cs.meta | 11 -- Runtime/AnimatedShaderProperty.cs | 158 -------------------- Runtime/AnimatedShaderProperty.cs.meta | 11 -- Runtime/ShaderProperty.cs | 73 --------- Runtime/ShaderProperty.cs.meta | 11 -- 6 files changed, 294 deletions(-) delete mode 100644 Editor/ShaderPropertyPropertyDrawer.cs delete mode 100644 Editor/ShaderPropertyPropertyDrawer.cs.meta delete mode 100644 Runtime/AnimatedShaderProperty.cs delete mode 100644 Runtime/AnimatedShaderProperty.cs.meta delete mode 100644 Runtime/ShaderProperty.cs delete mode 100644 Runtime/ShaderProperty.cs.meta diff --git a/Editor/ShaderPropertyPropertyDrawer.cs b/Editor/ShaderPropertyPropertyDrawer.cs deleted file mode 100644 index f74e01b..0000000 --- a/Editor/ShaderPropertyPropertyDrawer.cs +++ /dev/null @@ -1,30 +0,0 @@ -ο»Ώusing UnityEditor; -using UnityEngine; - -namespace Zigurous.Graphics.Editor -{ - [CustomPropertyDrawer(typeof(ShaderProperty))] - public sealed class ShaderPropertyPropertyDrawer : PropertyDrawer - { - public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) - { - SerializedProperty id = property.FindPropertyRelative("m_Id"); - SerializedProperty name = property.FindPropertyRelative("m_Name"); - - EditorGUI.BeginProperty(position, label, property); - position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label); - - string value = EditorGUI.TextField(position, name.stringValue); - - if (value != name.stringValue) - { - name.stringValue = value; - id.intValue = Shader.PropertyToID(value); - } - - EditorGUI.EndProperty(); - } - - } - -} diff --git a/Editor/ShaderPropertyPropertyDrawer.cs.meta b/Editor/ShaderPropertyPropertyDrawer.cs.meta deleted file mode 100644 index 59ec774..0000000 --- a/Editor/ShaderPropertyPropertyDrawer.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: cb6df95fcbc802f408a71ed0688373cc -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/AnimatedShaderProperty.cs b/Runtime/AnimatedShaderProperty.cs deleted file mode 100644 index b5197ec..0000000 --- a/Runtime/AnimatedShaderProperty.cs +++ /dev/null @@ -1,158 +0,0 @@ -ο»Ώusing System.Collections.Generic; -using UnityEngine; - -namespace Zigurous.Graphics -{ - /// - /// A shader property that can be animated. - /// - [System.Serializable] - public abstract class AnimatedShaderProperty - { - /// - /// The shader property to animate. - /// - [Tooltip("The shader property to animate.")] - public ShaderProperty property; - - /// - /// Creates a new animated shader property. - /// - /// The shader property to animate. - public AnimatedShaderProperty(string property) - { - this.property = property; - } - - /// - /// Animates the shader property. - /// - /// The material to animate. - /// The time of the animation to evaluate. - public abstract void Animate(Material material, float time); - - } - - /// - /// A shader float property that can be animated. - /// - [System.Serializable] - public class AnimatedShaderFloatProperty : AnimatedShaderProperty - { - /// - /// The value over time of the shader property. - /// - [Tooltip("The value over time of the shader property.")] - public AnimationCurve valueOverTime; - - /// - /// Creates a new animated shader float property. - /// - /// The value over time of the shader property. - /// The shader property to animate. - public AnimatedShaderFloatProperty(AnimationCurve valueOverTime, string property) : base(property) - { - this.valueOverTime = valueOverTime; - } - - /// - public override void Animate(Material material, float time) - { - material.SetFloat(property.id, valueOverTime.Evaluate(time)); - } - - } - - /// - /// A shader int property that can be animated. - /// - [System.Serializable] - public class AnimatedShaderIntProperty : AnimatedShaderProperty - { - /// - /// The value over time of the shader property. - /// - [Tooltip("The value over time of the shader property.")] - public AnimationCurve valueOverTime; - - /// - /// Creates a new animated shader int property. - /// - /// The value over time of the shader property. - /// The shader property to animate. - public AnimatedShaderIntProperty(AnimationCurve valueOverTime, string property) : base(property) - { - this.valueOverTime = valueOverTime; - } - - /// - public override void Animate(Material material, float time) - { - material.SetInt(property.id, (int)valueOverTime.Evaluate(time)); - } - - } - - /// - /// A shader color property that can be animated. - /// - [System.Serializable] - public class AnimatedShaderColorProperty : AnimatedShaderProperty - { - /// - /// The color over time of the shader property. - /// - public Gradient colorOverTime; - - /// - /// Creates a new animated shader color property. - /// - /// The color over time of the shader property. - /// The shader property to animate. - public AnimatedShaderColorProperty(Gradient colorOverTime, string property) : base(property) - { - this.colorOverTime = colorOverTime; - } - - /// - public override void Animate(Material material, float time) - { - material.SetColor(property.id, colorOverTime.Evaluate(time)); - } - - } - - /// - /// Extension methods for . - /// - public static class AnimatedShaderPropertyExtensions - { - /// - /// Animates an array of shader properties. - /// - /// The shader properties to animate. - /// The material to animate. - /// The time of the animation to evaluate. - public static void Animate(this AnimatedShaderProperty[] properties, Material material, float time) - { - for (int i = 0; i < properties.Length; i++) { - properties[i].Animate(material, time); - } - } - - /// - /// Animates an array of shader properties. - /// - /// The shader properties to animate. - /// The material to animate. - /// The time of the animation to evaluate. - public static void Animate(this List properties, Material material, float time) - { - foreach (AnimatedShaderProperty property in properties) { - property.Animate(material, time); - } - } - - } - -} diff --git a/Runtime/AnimatedShaderProperty.cs.meta b/Runtime/AnimatedShaderProperty.cs.meta deleted file mode 100644 index 9672563..0000000 --- a/Runtime/AnimatedShaderProperty.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: a53bfe0a27b00db4f905734ac4f77ad0 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/ShaderProperty.cs b/Runtime/ShaderProperty.cs deleted file mode 100644 index 8ca0613..0000000 --- a/Runtime/ShaderProperty.cs +++ /dev/null @@ -1,73 +0,0 @@ -ο»Ώusing UnityEngine; - -namespace Zigurous.Graphics -{ - /// - /// A shader property that can be set on a material. An id is automatically - /// created for the property for optimal code. - /// - [System.Serializable] - public struct ShaderProperty - { - [SerializeField] - [HideInInspector] - private int m_Id; - - /// - /// The id of the shader property (Read only). - /// - public int id - { - get - { - if (m_Id == 0) { - m_Id = Shader.PropertyToID(m_Name); - } - return m_Id; - } - } - - [SerializeField] - [Tooltip("The name of the shader property.")] - private string m_Name; - - /// - /// The name of the shader property. - /// - public string name - { - get => m_Name; - set - { - m_Name = value; - m_Id = Shader.PropertyToID(value); - } - } - - /// - /// Creates a new shader property with the given name. - /// - /// The name of the shader property. - public ShaderProperty(string name) - { - m_Name = name; - m_Id = Shader.PropertyToID(name); - } - - /// - /// Implicitly converts a name to a shader property. - /// - /// The name of the shader property. - /// A shader property with the given name. - public static implicit operator ShaderProperty(string name) => new ShaderProperty(name); - - /// - /// Implicitly converts a shader property to an id. - /// - /// The shader property to convert to an id. - /// The id of the shader property. - public static implicit operator int(ShaderProperty property) => property.id; - - } - -} diff --git a/Runtime/ShaderProperty.cs.meta b/Runtime/ShaderProperty.cs.meta deleted file mode 100644 index ad4dd92..0000000 --- a/Runtime/ShaderProperty.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: db26498e97261284aa5e11a8e8a022e9 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: From 493a30c24452ddae57af2da44ae4e6dde72abc55 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 14:32:27 -0500 Subject: [PATCH 26/41] Rename AutoTile.Submesh to AutoTile.SubmeshTiling --- Runtime/AutoTile.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Runtime/AutoTile.cs b/Runtime/AutoTile.cs index 9041bc4..3a0f922 100644 --- a/Runtime/AutoTile.cs +++ b/Runtime/AutoTile.cs @@ -11,7 +11,7 @@ namespace Zigurous.Graphics { /// - /// Automatically tiles the material textures based on the object's scale. + /// Automatically tiles the material's textures based on the object's scale. /// [ExecuteAlways] [AddComponentMenu("Zigurous/Graphics/Auto Tile")] @@ -59,7 +59,7 @@ public enum Axis /// A representation of a submesh that can be individually tiled. /// [System.Serializable] - public sealed class Submesh + public sealed class SubmeshTiling { #if UNITY_EDITOR /// @@ -103,7 +103,7 @@ public sealed class Submesh /// The submeshes that are tiled on the renderer. /// [Tooltip("The submeshes that are tiled on the renderer.")] - public Submesh[] submeshes = new Submesh[1] { new Submesh() }; + public SubmeshTiling[] submeshes = new SubmeshTiling[1] { new SubmeshTiling() }; /// /// The names of the textures that are tiled on the material. @@ -136,11 +136,11 @@ private void Reset() if (mesh != null) { - submeshes = new Submesh[mesh.subMeshCount]; + submeshes = new SubmeshTiling[mesh.subMeshCount]; for (int i = 0; i < submeshes.Length; i++) { - Submesh submesh = new Submesh(); + SubmeshTiling submesh = new SubmeshTiling(); submesh.submeshIndex = i; submeshes[i] = submesh; } @@ -204,7 +204,7 @@ private void UpdateMaterials(Material[] materials) { for (int i = 0; i < submeshes.Length; i++) { - Submesh submesh = submeshes[i]; + SubmeshTiling submesh = submeshes[i]; if (submesh.submeshIndex >= 0 && submesh.submeshIndex < materials.Length) { UpdateMaterial(materials[submesh.submeshIndex], submesh); @@ -217,7 +217,7 @@ private void UpdateMaterialsInEditor(Material[] materials) { for (int i = 0; i < submeshes.Length; i++) { - Submesh submesh = submeshes[i]; + SubmeshTiling submesh = submeshes[i]; if (submesh.submeshIndex >= 0 && submesh.submeshIndex < materials.Length) { @@ -241,7 +241,7 @@ private void UpdateMaterialsInEditor(Material[] materials) } #endif - private void UpdateMaterial(Material material, Submesh submesh) + private void UpdateMaterial(Material material, SubmeshTiling submesh) { if (material == null || textureNames == null) { return; From f525a9541155cf8f94d0c7859682935bbcd85e29 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 14:34:39 -0500 Subject: [PATCH 27/41] Move precompiler conditions inside function --- Runtime/Extensions/MeshExtensions.cs | 6 ++--- Runtime/Extensions/MeshFilterExtensions.cs | 26 +++++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Runtime/Extensions/MeshExtensions.cs b/Runtime/Extensions/MeshExtensions.cs index 0b72acb..ce46cf9 100644 --- a/Runtime/Extensions/MeshExtensions.cs +++ b/Runtime/Extensions/MeshExtensions.cs @@ -1,4 +1,4 @@ -ο»Ώ#if UNITY_EDITOR +#if UNITY_EDITOR using UnityEditor; #endif using UnityEngine; @@ -10,7 +10,6 @@ namespace Zigurous.Graphics /// public static class MeshExtensions { - #if UNITY_EDITOR /// /// Saves the mesh as a project asset (Editor only). /// @@ -18,11 +17,12 @@ public static class MeshExtensions /// The name to save the asset as. public static void Save(this Mesh mesh, string assetName) { + #if UNITY_EDITOR if (mesh != null) { AssetDatabase.CreateAsset(mesh, "Assets/" + assetName + ".mesh"); } + #endif } - #endif /// /// Returns a new copy of the mesh. diff --git a/Runtime/Extensions/MeshFilterExtensions.cs b/Runtime/Extensions/MeshFilterExtensions.cs index 6d518d7..90e15c8 100644 --- a/Runtime/Extensions/MeshFilterExtensions.cs +++ b/Runtime/Extensions/MeshFilterExtensions.cs @@ -7,20 +7,24 @@ namespace Zigurous.Graphics { public static class MeshFilterExtensions { - #if UNITY_EDITOR /// /// Saves the mesh of the filter as a project asset (Editor only). /// /// The mesh filter to save the mesh of. public static void SaveMesh(this MeshFilter filter) { - if (filter != null && filter.mesh != null) { - AssetDatabase.CreateAsset(filter.mesh, "Assets/" + filter.mesh.name + ".mesh"); + #if UNITY_EDITOR + if (filter != null) + { + if (Application.isPlaying) { + AssetDatabase.CreateAsset(filter.mesh, $"Assets/{filter.mesh.name}.mesh"); + } else { + AssetDatabase.CreateAsset(filter.sharedMesh, $"Assets/{filter.mesh.name}.mesh"); + } } + #endif } - #endif - #if UNITY_EDITOR /// /// Saves the mesh of the filter as a project asset (Editor only). /// @@ -28,11 +32,17 @@ public static void SaveMesh(this MeshFilter filter) /// The name to save the asset as. public static void SaveMesh(this MeshFilter filter, string assetName) { - if (filter != null && filter.mesh != null) { - AssetDatabase.CreateAsset(filter.mesh, "Assets/" + assetName + ".mesh"); + #if UNITY_EDITOR + if (filter != null) + { + if (Application.isPlaying) { + AssetDatabase.CreateAsset(filter.mesh, $"Assets/{assetName}.mesh"); + } else { + AssetDatabase.CreateAsset(filter.sharedMesh, $"Assets/{assetName}.mesh"); + } } + #endif } - #endif /// /// Combines the meshes of the mesh filters into one mesh. From 00737965d3614e60478f31d86da4fbf48f3df450 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 14:34:53 -0500 Subject: [PATCH 28/41] Add RecalculateUV extension method --- Runtime/Extensions/MeshExtensions.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Runtime/Extensions/MeshExtensions.cs b/Runtime/Extensions/MeshExtensions.cs index ce46cf9..9966936 100644 --- a/Runtime/Extensions/MeshExtensions.cs +++ b/Runtime/Extensions/MeshExtensions.cs @@ -1,4 +1,4 @@ -#if UNITY_EDITOR +ο»Ώ#if UNITY_EDITOR using UnityEditor; #endif using UnityEngine; @@ -135,6 +135,24 @@ public static int[] InvertedTriangles(this Mesh mesh, int submesh) return triangles; } + /// + /// Calculates and assigns the UV coordinates of the mesh using its verticies. + /// + /// The mesh to calculate and assign the UV coordinates to. + public static void RecalculateUV(this Mesh mesh) + { + Bounds bounds = mesh.bounds; + + Vector3[] verticies = mesh.vertices; + Vector2[] uvs = new Vector2[verticies.Length]; + + for (int i = 0; i < verticies.Length; i++) { + uvs[i] = new Vector2(verticies[i].x / bounds.size.x, verticies[i].y / bounds.size.y); + } + + mesh.uv = uvs; + } + } } From 44392658fb31fb8ac737afba1b6578c3a3183c92 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 14:37:49 -0500 Subject: [PATCH 29/41] Add context menu to save mesh from editor --- Runtime/SaveMesh.cs | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/Runtime/SaveMesh.cs b/Runtime/SaveMesh.cs index 103a275..58823e6 100644 --- a/Runtime/SaveMesh.cs +++ b/Runtime/SaveMesh.cs @@ -1,9 +1,12 @@ -ο»Ώusing UnityEngine; +ο»Ώ#if UNITY_EDITOR +using UnityEditor; +#endif +using UnityEngine; namespace Zigurous.Graphics { /// - /// Saves the mesh of a mesh filter into a project asset. + /// Saves the mesh of a mesh filter as a project asset. /// [AddComponentMenu("Zigurous/Graphics/Save Mesh")] [HelpURL("https://docs.zigurous.com/com.zigurous.graphics/api/Zigurous.Graphics/SaveMesh")] @@ -14,7 +17,7 @@ public sealed class SaveMesh : MonoBehaviour /// The name of the saved asset. The mesh name will be used if not set. /// [Tooltip("The name of the saved asset. The mesh name will be used if not set.")] - public string assetName; + public string assetName = "New Mesh"; /// /// Saves the mesh on start, otherwise it needs to be called manually. @@ -38,18 +41,17 @@ private void Reset() assetName = filter.sharedMesh.name; } } + + enabled = false; } private void Start() { - #if UNITY_EDITOR if (saveOnStart) { Save(); } - #endif } - #if UNITY_EDITOR /// /// Saves the mesh as a project asset. /// @@ -57,10 +59,24 @@ public void Save() { MeshFilter filter = GetComponent(); - if (assetName.Length > 0) { - filter.SaveMesh(assetName); - } else { + if (string.IsNullOrEmpty(assetName)) { filter.SaveMesh(); + } else { + filter.SaveMesh(assetName); + } + } + + #if UNITY_EDITOR + [MenuItem("CONTEXT/SaveMesh/Save Mesh")] + private static void ContextMenu_Save() + { + if (Selection.activeGameObject != null) + { + SaveMesh mesh = Selection.activeGameObject.GetComponent(); + + if (mesh != null) { + mesh.Save(); + } } } #endif From f40c7eec1d5f57988361be6e17785a87ea7983a9 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 14:38:40 -0500 Subject: [PATCH 30/41] Improve CombineChildrenMeshes behavior --- Runtime/CombineChildrenMeshes.cs | 36 +++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/Runtime/CombineChildrenMeshes.cs b/Runtime/CombineChildrenMeshes.cs index a11d7e5..9db9eaa 100644 --- a/Runtime/CombineChildrenMeshes.cs +++ b/Runtime/CombineChildrenMeshes.cs @@ -8,9 +8,14 @@ namespace Zigurous.Graphics [AddComponentMenu("Zigurous/Graphics/Combine Children Meshes")] [HelpURL("https://docs.zigurous.com/com.zigurous.graphics/api/Zigurous.Graphics/CombineChildrenMeshes")] [RequireComponent(typeof(MeshFilter))] - [RequireComponent(typeof(MeshRenderer))] public sealed class CombineChildrenMeshes : MonoBehaviour { + /// + /// The name of the combined mesh. + /// + [Tooltip("The name of the combined mesh.")] + public string combinedMeshName = "Combined Mesh"; + /// /// Combines the mesh on start, otherwise it needs to be called manually. /// @@ -18,10 +23,10 @@ public sealed class CombineChildrenMeshes : MonoBehaviour public bool combineOnStart = true; /// - /// Removes the child meshes from the game object after combining. + /// Destroys the child game objects after combining. /// - [Tooltip("Removes the child meshes from the game object after combining.")] - public bool removeChildMeshes = true; + [Tooltip("Destroys the child game objects after combining.")] + public bool deleteChildren = true; /// /// Combines all of the meshes into a single submesh. @@ -41,6 +46,11 @@ public sealed class CombineChildrenMeshes : MonoBehaviour [Tooltip("Recalculates the bounding volume of the combined mesh.")] public bool recalculateBounds = true; + private void Reset() + { + enabled = false; + } + private void Start() { MeshFilter parent = GetComponent(); @@ -57,7 +67,7 @@ private void Start() public Mesh Combine() { MeshFilter[] children = GetComponentsInChildren(); - CombineInstance[] combine = new CombineInstance[children.Length - 1]; + CombineInstance[] combine = new CombineInstance[children.Length]; int submesh = 0; @@ -65,28 +75,30 @@ public Mesh Combine() { MeshFilter child = children[i]; - // Ignore the parent mesh - if (child.transform == transform) { + if (child.mesh == null) { continue; } // Create a mesh combine instance CombineInstance instance = new CombineInstance(); instance.mesh = child.mesh; - instance.transform = Matrix4x4.TRS(child.transform.localPosition, child.transform.localRotation, child.transform.localScale); + instance.transform = child.transform.localToWorldMatrix; combine[submesh++] = instance; // Destroy the child mesh - if (removeChildMeshes) + if (child.transform != this.transform) { - Destroy(child.GetComponent()); - Destroy(child); + if (deleteChildren) { + Destroy(child.gameObject); + } else { + child.gameObject.SetActive(false); + } } } // Create a new mesh from all of the combined children Mesh combinedMesh = new Mesh(); - combinedMesh.name = "Combined Mesh"; + combinedMesh.name = combinedMeshName; combinedMesh.CombineMeshes(combine, mergeSubmeshes); if (optimizeMesh) { From 7b39258fb8775d0e9865861e73b585ed7f77a046 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 14:47:16 -0500 Subject: [PATCH 31/41] Refactor TextureDrawer as a ScriptableObject and separate renderer script --- Runtime/CheckerboardTextureDrawer.cs | 87 +++------- Runtime/TextureDrawer.cs | 219 ++------------------------ Runtime/TextureDrawerRenderer.cs | 162 +++++++++++++++++++ Runtime/TextureDrawerRenderer.cs.meta | 11 ++ 4 files changed, 212 insertions(+), 267 deletions(-) create mode 100644 Runtime/TextureDrawerRenderer.cs create mode 100644 Runtime/TextureDrawerRenderer.cs.meta diff --git a/Runtime/CheckerboardTextureDrawer.cs b/Runtime/CheckerboardTextureDrawer.cs index 6062846..3e2239d 100644 --- a/Runtime/CheckerboardTextureDrawer.cs +++ b/Runtime/CheckerboardTextureDrawer.cs @@ -6,73 +6,46 @@ namespace Zigurous.Graphics /// Draws a texture of a checkerboard pattern. /// [AddComponentMenu("Zigurous/Graphics/Checkerboard Texture Drawer")] + [CreateAssetMenu(menuName = "Zigurous/Graphics/Checkerboard Texture Drawer")] [HelpURL("https://docs.zigurous.com/com.zigurous.graphics/api/Zigurous.Graphics/CheckerboardTextureDrawer")] public sealed class CheckerboardTextureDrawer : TextureDrawer { /// - /// The checkerboard settings of a . + /// The number of rows in the checkerboard. /// - [System.Serializable] - public struct CheckerboardSettings - { - /// - /// The number of rows in the checkerboard. - /// - [Tooltip("The number of rows in the checkerboard.")] - public int rows; - - /// - /// The number of columns in the checkerboard. - /// - [Tooltip("The number of columns in the checkerboard.")] - public int columns; - - /// - /// The first color of the checkerboard. - /// - [Tooltip("The first color of the checkerboard.")] - public Color colorA; + [Header("Checkerboard Settings")] + [Tooltip("The number of rows in the checkerboard.")] + public int rows = 4; - /// - /// The second color of the checkerboard. - /// - [Tooltip("The second color of the checkerboard.")] - public Color colorB; - - /// - /// Creates new checkerboard settings with the specified values. - /// - /// The number of rows in the checkerboard. - /// The number of columns in the checkerboard. - /// The first color of the checkerboard. - /// The second color of the checkerboard. - public CheckerboardSettings(int rows, int columns, Color colorA, Color colorB) - { - this.rows = rows; - this.columns = columns; - this.colorA = colorA; - this.colorB = colorB; - } + /// + /// The number of columns in the checkerboard. + /// + [Tooltip("The number of columns in the checkerboard.")] + public int columns = 4; - } + /// + /// The first color of the checkerboard. + /// + [Tooltip("The first color of the checkerboard.")] + public Color colorA = Color.white; /// - /// The checkerboard settings. + /// The second color of the checkerboard. /// - [Tooltip("The checkerboard settings.")] - public CheckerboardSettings checkerboard = new CheckerboardSettings(4, 4, Color.white, Color.black); + [Tooltip("The second color of the checkerboard.")] + public Color colorB = Color.black; /// public override void SetPixels(Texture2D texture) { - int rectWidth = texture.width / checkerboard.columns; - int rectHeight = texture.height / checkerboard.rows; + int rectWidth = texture.width / columns; + int rectHeight = texture.height / rows; - for (int row = 0; row < checkerboard.rows; row++) + for (int row = 0; row < rows; row++) { - for (int col = 0; col < checkerboard.columns; col++) + for (int col = 0; col < columns; col++) { - Color color = (row + col) % 2 == 0 ? checkerboard.colorA : checkerboard.colorB; + Color color = (row + col) % 2 == 0 ? colorA : colorB; int positionX = col * rectWidth; int positionY = row * rectHeight; @@ -88,20 +61,6 @@ public override void SetPixels(Texture2D texture) } } - /// - protected override void OnValidate() - { - base.OnValidate(); - - if (checkerboard.rows < 2) { - checkerboard.rows = 2; - } - - if (checkerboard.columns < 2) { - checkerboard.columns = 2; - } - } - } } diff --git a/Runtime/TextureDrawer.cs b/Runtime/TextureDrawer.cs index 58fd1fd..9416d67 100644 --- a/Runtime/TextureDrawer.cs +++ b/Runtime/TextureDrawer.cs @@ -3,180 +3,33 @@ namespace Zigurous.Graphics { /// - /// Draws a custom texture at runtime. + /// The base class to draw a custom texture at runtime. /// - [ExecuteAlways] - public abstract class TextureDrawer : MonoBehaviour + public abstract class TextureDrawer : ScriptableObject { - /// - /// The texture settings of a . - /// - [System.Serializable] - public struct TextureSettings - { - /// - /// The width and height of the texture. - /// - [Tooltip("The width and height of the texture.")] - public Vector2Int size; - - /// - /// The filter mode of the texture. - /// - [Tooltip("The filter mode of the texture.")] - public FilterMode filterMode; - - /// - /// The wrap mode of the texture. - /// - [Tooltip("The wrap mode of the texture.")] - public TextureWrapMode wrapMode; - - /// - /// Creates new texture settings with specified values. - /// - /// The size of the texture. - /// The filter mode of the texture. - /// The wrap mode of the texture. - public TextureSettings(Vector2Int size, FilterMode filterMode, TextureWrapMode wrapMode) - { - this.size = size; - this.filterMode = filterMode; - this.wrapMode = wrapMode; - } - - } - - /// - /// The render settings of a . - /// - [System.Serializable] - public struct RenderSettings - { - /// - /// The shader property that holds the texture. - /// - [Tooltip("The shader property that holds the texture.")] - public ShaderProperty shaderTextureName; - - /// - /// The amount of scaling to apply to the transform (as a multiplier). - /// - [Tooltip("The amount of scaling to apply to the transform (as a multiplier).")] - public float scaleFactor; - - /// - /// Scales the transform of the object to match the texture size. - /// - [Tooltip("Scales the transform of the object to match the texture size.")] - public bool scaleTransform; - - #if UNITY_EDITOR - /// - /// Updates the texture while in the editor. - /// - [Tooltip("Updates the texture while in the editor.")] - public bool updateInEditor; - #endif - - /// - /// Creates new render settings with the specified values. - /// - /// The shader property that holds the texture. - /// The amount of scaling to apply to the transform (as a multiplier). - /// Scales the transform of the object to match the texture size. - public RenderSettings(ShaderProperty shaderTextureName, float scaleFactor = 1f, bool scaleTransform = false) - { - this.shaderTextureName = shaderTextureName; - this.scaleFactor = scaleFactor; - this.scaleTransform = scaleTransform; - - #if UNITY_EDITOR - this.updateInEditor = false; - #endif - } - - } - /// /// The drawn texture (Read only). /// public Texture2D texture { get; private set; } /// - /// The texture settings. - /// - [Tooltip("The texture settings.")] - public TextureSettings textureSettings = new TextureSettings(new Vector2Int(1024, 1024), FilterMode.Bilinear, TextureWrapMode.Clamp); - - /// - /// The renderer component that holds the material the texture is added - /// to (Read only). - /// - public new Renderer renderer { get; private set; } - - /// - /// The render settings. + /// The width and height of the texture. /// - [Tooltip("The render settings.")] - public RenderSettings renderSettings = new RenderSettings("_MainTex", 1f, false); - - /// - /// Whether the settings have changed since the texture was last drawn - /// (Read only). - /// - public bool invalidated { get; internal set; } - - /// - /// A Unity lifecycle method called when the behavior is initialized. - /// - protected virtual void Awake() - { - renderer = GetComponent(); - } + [Header("Texture Settings")] + [Tooltip("The width and height of the texture.")] + public Vector2Int size = new Vector2Int(1024, 1024); /// - /// A Unity lifecycle method called when the behavior is enabled. + /// The filter mode of the texture. /// - protected virtual void OnEnable() - { - if (Application.isPlaying) { - Draw(); - } - } + [Tooltip("The filter mode of the texture.")] + public FilterMode filterMode = FilterMode.Bilinear; /// - /// A Unity lifecycle method called during editor validation. + /// The wrap mode of the texture. /// - protected virtual void OnValidate() - { - invalidated = true; - - if (textureSettings.size.x < 1) { - textureSettings.size.x = 1; - } - - if (textureSettings.size.y < 1) { - textureSettings.size.y = 1; - } - - if (renderer == null) { - renderer = GetComponent(); - } - } - - private void Update() - { - #if UNITY_EDITOR - if (!(Application.isPlaying || renderSettings.updateInEditor)) { - return; - } - #endif - - if (invalidated) { - Draw(); - } - } + [Tooltip("The wrap mode of the texture.")] + public TextureWrapMode wrapMode = TextureWrapMode.Clamp; /// /// Draws the texture. @@ -184,59 +37,19 @@ private void Update() /// The drawn texture. public Texture2D Draw() { - if (texture == null || texture.width != textureSettings.size.x || texture.height != textureSettings.size.y) { - texture = new Texture2D(textureSettings.size.x, textureSettings.size.y); + if (texture == null || texture.width != size.x || texture.height != size.y) { + texture = new Texture2D(size.x, size.y); } - texture.filterMode = textureSettings.filterMode; - texture.wrapMode = textureSettings.wrapMode; - invalidated = false; + texture.filterMode = filterMode; + texture.wrapMode = wrapMode; SetPixels(texture); - texture.Apply(); - ApplyTexture(); - SetTransformScale(); - return texture; } - /// - /// Applies the texture to the renderer material. - /// - private void ApplyTexture() - { - if (renderer == null) { - return; - } - - if (Application.isPlaying) { - renderer.material.SetTexture(renderSettings.shaderTextureName.id, texture); - } else { - renderer.sharedMaterial = new Material(renderer.sharedMaterial); - renderer.sharedMaterial.SetTexture(renderSettings.shaderTextureName.id, texture); - } - } - - /// - /// Sets the scale of the transform based on the texture size. - /// - private void SetTransformScale() - { - if (texture == null || !renderSettings.scaleTransform) { - return; - } - - Vector3 scale = new Vector2(texture.width, texture.height); - scale *= renderSettings.scaleFactor; - scale.z = 1f; - - if (scale.x != Mathf.Infinity && scale.y != Mathf.Infinity) { - transform.localScale = scale; - } - } - /// /// Sets the pixels of the texture. /// diff --git a/Runtime/TextureDrawerRenderer.cs b/Runtime/TextureDrawerRenderer.cs new file mode 100644 index 0000000..c2e84e6 --- /dev/null +++ b/Runtime/TextureDrawerRenderer.cs @@ -0,0 +1,162 @@ +ο»Ώusing UnityEditor; +using UnityEngine; + +namespace Zigurous.Graphics +{ + [ExecuteAlways] + [RequireComponent(typeof(Renderer))] + public sealed class TextureDrawerRenderer : MonoBehaviour + { + private Renderer m_Renderer; + + [Tooltip("The drawer that creates the texture.")] + [SerializeField] private TextureDrawer m_Drawer; + + [Tooltip("The shader property that holds the texture.")] + [SerializeField] private string m_ShaderTextureName = "_MainTex"; + + [Tooltip("The amount of scaling to apply to the transform (as a multiplier).")] + [SerializeField] private float m_ScaleFactor = 1f; + + [Tooltip("Scales the transform of the object to match the texture size.")] + [SerializeField] private bool m_ScaleTransform = false; + + /// + /// The drawer that creates the texture. + /// + public TextureDrawer drawer + { + get => m_Drawer; + set + { + m_Drawer = value; + Render(); + } + } + + /// + /// The shader property that holds the texture. + /// + public string shaderTextureName + { + get => m_ShaderTextureName; + set + { + m_ShaderTextureName = value; + Render(); + } + } + + /// + /// The amount of scaling to apply to the transform (as a multiplier). + /// + public float scaleFactor + { + get => m_ScaleFactor; + set + { + m_ScaleFactor = value; + Render(); + } + } + + /// + /// Scales the transform of the object to match the texture size. + /// + public bool scaleTransform + { + get => m_ScaleTransform; + set + { + m_ScaleTransform = value; + Render(); + } + } + + #if UNITY_EDITOR + [SerializeField] + private bool m_UpdateInEditor; + private bool m_Invalidated; + + private void OnValidate() + { + if (m_UpdateInEditor) { + m_Invalidated = true; + } + } + + private void Update() + { + if (m_Invalidated) + { + ForceRender(); + m_Invalidated = false; + } + } + #endif + + private void OnEnable() + { + Render(); + } + + private void Render() + { + if (enabled && Application.isPlaying) { + ForceRender(); + } + } + + private void ForceRender() + { + if (drawer == null) { + return; + } + + if (m_Renderer == null) { + m_Renderer = GetComponent(); + } + + Texture2D texture = drawer.Draw(); + + if (Application.isPlaying) { + m_Renderer.material.SetTexture(shaderTextureName, texture); + } else { + m_Renderer.sharedMaterial = new Material(m_Renderer.sharedMaterial); + m_Renderer.sharedMaterial.SetTexture(shaderTextureName, texture); + } + + if (scaleTransform) + { + Vector3 scale = new Vector2(texture.width, texture.height) * scaleFactor; + scale.z = 1f; + + if (scale.x != Mathf.Infinity && scale.y != Mathf.Infinity) { + transform.localScale = scale; + } + } + } + + #if UNITY_EDITOR + [MenuItem("CONTEXT/TextureDrawerRenderer/Force Update")] + private static void ForceUpdate() + { + if (Selection.activeGameObject != null) + { + TextureDrawerRenderer renderer = Selection.activeGameObject.GetComponent(); + + if (renderer != null) + { + if (renderer.m_UpdateInEditor) { + renderer.m_Invalidated = true; + } else if (Application.isPlaying) { + renderer.ForceRender(); + } + } + } + } + #endif + + } + +} diff --git a/Runtime/TextureDrawerRenderer.cs.meta b/Runtime/TextureDrawerRenderer.cs.meta new file mode 100644 index 0000000..e265448 --- /dev/null +++ b/Runtime/TextureDrawerRenderer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0794de26c7ce8c94ebe99c53ff5ba1ed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 796ad4c16e3d762a5d9c9c9c660af9128872ae29 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 15:04:22 -0500 Subject: [PATCH 32/41] Change prime number used to combine hash codes --- Runtime/Triangle.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Runtime/Triangle.cs b/Runtime/Triangle.cs index d404734..fd5d0ac 100644 --- a/Runtime/Triangle.cs +++ b/Runtime/Triangle.cs @@ -102,9 +102,9 @@ public override int GetHashCode() unchecked // Overflow is fine, just wrap { int hash = 17; - hash = hash * 23 + v1.GetHashCode(); - hash = hash * 23 + v2.GetHashCode(); - hash = hash * 23 + v3.GetHashCode(); + hash = hash * 31 + v1.GetHashCode(); + hash = hash * 31 + v2.GetHashCode(); + hash = hash * 31 + v3.GetHashCode(); return hash; } } From 27bf7d7760930411ca518997950700c775954507 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 15:04:49 -0500 Subject: [PATCH 33/41] Set Help URLs and component menu paths --- Runtime/CheckerboardTextureDrawer.cs | 1 - Runtime/TextureDrawer.cs | 1 + Runtime/TextureDrawerRenderer.cs | 2 ++ 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Runtime/CheckerboardTextureDrawer.cs b/Runtime/CheckerboardTextureDrawer.cs index 3e2239d..5e6099f 100644 --- a/Runtime/CheckerboardTextureDrawer.cs +++ b/Runtime/CheckerboardTextureDrawer.cs @@ -5,7 +5,6 @@ namespace Zigurous.Graphics /// /// Draws a texture of a checkerboard pattern. /// - [AddComponentMenu("Zigurous/Graphics/Checkerboard Texture Drawer")] [CreateAssetMenu(menuName = "Zigurous/Graphics/Checkerboard Texture Drawer")] [HelpURL("https://docs.zigurous.com/com.zigurous.graphics/api/Zigurous.Graphics/CheckerboardTextureDrawer")] public sealed class CheckerboardTextureDrawer : TextureDrawer diff --git a/Runtime/TextureDrawer.cs b/Runtime/TextureDrawer.cs index 9416d67..89f2af1 100644 --- a/Runtime/TextureDrawer.cs +++ b/Runtime/TextureDrawer.cs @@ -5,6 +5,7 @@ namespace Zigurous.Graphics /// /// The base class to draw a custom texture at runtime. /// + [HelpURL("https://docs.zigurous.com/com.zigurous.graphics/api/Zigurous.Graphics/TextureDrawer")] public abstract class TextureDrawer : ScriptableObject { /// diff --git a/Runtime/TextureDrawerRenderer.cs b/Runtime/TextureDrawerRenderer.cs index c2e84e6..56bc82d 100644 --- a/Runtime/TextureDrawerRenderer.cs +++ b/Runtime/TextureDrawerRenderer.cs @@ -4,6 +4,8 @@ namespace Zigurous.Graphics { [ExecuteAlways] + [AddComponentMenu("Zigurous/Graphics/Texture Drawer Renderer")] + [HelpURL("https://docs.zigurous.com/com.zigurous.graphics/api/Zigurous.Graphics/TextureDrawerRenderer")] [RequireComponent(typeof(Renderer))] public sealed class TextureDrawerRenderer : MonoBehaviour { From 3bcb1ac92c3e37e35dea985a71c6c8334866d357 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 15:50:59 -0500 Subject: [PATCH 34/41] Update CombineMeshes extension to match behavior --- Runtime/Extensions/MeshFilterExtensions.cs | 23 ++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Runtime/Extensions/MeshFilterExtensions.cs b/Runtime/Extensions/MeshFilterExtensions.cs index 90e15c8..93c89fc 100644 --- a/Runtime/Extensions/MeshFilterExtensions.cs +++ b/Runtime/Extensions/MeshFilterExtensions.cs @@ -5,6 +5,9 @@ namespace Zigurous.Graphics { + /// + /// Extension methods for mesh filters. + /// public static class MeshFilterExtensions { /// @@ -48,24 +51,32 @@ public static void SaveMesh(this MeshFilter filter, string assetName) /// Combines the meshes of the mesh filters into one mesh. /// /// The mesh filters to combine. + /// The name of the new combined mesh. /// Optimizes the combined mesh data to improve rendering performance. /// Recalculates the bounding volume of the combined mesh. /// The combined mesh. - public static Mesh CombineMeshes(this MeshFilter[] filters, bool optimizeMesh = true, bool recalculateBounds = true) + public static Mesh CombineMeshes(this MeshFilter[] filters, string combinedMeshName = "Combined Mesh", bool optimizeMesh = true, bool recalculateBounds = true) { CombineInstance[] combine = new CombineInstance[filters.Length]; + int submesh = 0; + for (int i = 0; i < filters.Length; i++) { - MeshFilter child = filters[i]; + MeshFilter filter = filters[i]; + + if (filter.mesh == null) { + continue; + } + CombineInstance instance = new CombineInstance(); - instance.mesh = child.mesh; - instance.transform = Matrix4x4.TRS(child.transform.localPosition, child.transform.localRotation, child.transform.localScale); - combine[i] = instance; + instance.mesh = filter.mesh; + instance.transform = filter.transform.localToWorldMatrix; + combine[submesh++] = instance; } Mesh combinedMesh = new Mesh(); - combinedMesh.name = "Combined Mesh"; + combinedMesh.name = combinedMeshName; combinedMesh.CombineMeshes(combine); if (optimizeMesh) { From b9460baa697a8edff7576bc37f2668cf9d8e3719 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 15:51:17 -0500 Subject: [PATCH 35/41] Use string interpolation instead of concatenation --- Runtime/Extensions/MeshExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Runtime/Extensions/MeshExtensions.cs b/Runtime/Extensions/MeshExtensions.cs index 9966936..7456141 100644 --- a/Runtime/Extensions/MeshExtensions.cs +++ b/Runtime/Extensions/MeshExtensions.cs @@ -19,7 +19,7 @@ public static void Save(this Mesh mesh, string assetName) { #if UNITY_EDITOR if (mesh != null) { - AssetDatabase.CreateAsset(mesh, "Assets/" + assetName + ".mesh"); + AssetDatabase.CreateAsset(mesh, $"Assets/{assetName}.mesh"); } #endif } From 3f1391066e574dd954b5a24e9a20bfe1a9f1493a Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 17:26:25 -0500 Subject: [PATCH 36/41] Add new texture extension methods --- Runtime/Extensions/TextureExtensions.cs | 116 +++++++++++++++++++ Runtime/Extensions/TextureExtensions.cs.meta | 11 ++ 2 files changed, 127 insertions(+) create mode 100644 Runtime/Extensions/TextureExtensions.cs create mode 100644 Runtime/Extensions/TextureExtensions.cs.meta diff --git a/Runtime/Extensions/TextureExtensions.cs b/Runtime/Extensions/TextureExtensions.cs new file mode 100644 index 0000000..fbc0bdf --- /dev/null +++ b/Runtime/Extensions/TextureExtensions.cs @@ -0,0 +1,116 @@ +ο»Ώusing UnityEngine; + +namespace Zigurous.Graphics +{ + /// + /// Extension methods for textures. + /// + public static class TextureExtensions + { + /// + /// Maps the UV coordinates in the range [0..1] to pixel coordinates + /// in the range [0..width-1] and [0..height-1]. + /// + /// The texture to get the pixel coordinates from. + /// The UV coordinate in the x-axis. + /// The UV coordinate in the y-axis. + /// The output pixel coordinate in the x-axis. + /// The output pixel coordinate in the y-axis. + public static void GetPixelCoordinates(this Texture2D texture, float u, float v, out int px, out int py) + { + px = (int)Mathf.Lerp(0, texture.width - 1, u); + py = (int)Mathf.Lerp(0, texture.height - 1, v); + } + + /// + /// Maps the pixel coordinates in the range [0..width-1] and + /// [0..height-1] to UV coordinates in the range [0..1]. + /// + /// The texture to get the UV coordinates from. + /// The pixel coordinate in the x-axis. + /// The pixel coordinate in the y-axis. + /// The output UV coordinate in the x-axis. + /// The output UV coordinate in the y-axis. + public static void GetUVCoordinates(this Texture2D texture, int px, int py, out float u, out float v) + { + u = Mathf.InverseLerp(0, texture.width - 1, px); + v = Mathf.InverseLerp(0, texture.height - 1, py); + } + + /// + /// Gets the pixel color at the specified UV coordinates. + /// + /// The texture to sample from. + /// The UV coordinate in the x-axis. + /// The UV coordinate in the y-axis. + /// The pixel color. + public static Color Sample(this Texture2D texture, float u, float v) + { + if (texture == null || texture.width == 0 || texture.height == 0) { + return Color.clear; + } + + texture.GetPixelCoordinates(u, v, out int px, out int py); + return texture.GetPixel(px, py); + } + + /// + /// Gets the pixel color at the UV coordinates calculated from a point + /// inside a rectangle. + /// + /// The texture to sample from. + /// The rectangle to sample from. + /// The point inside the rectangle. + /// The pixel color. + public static Color Sample(this Texture2D texture, Rect rect, Vector2 point) + { + if (texture == null || texture.width == 0 || texture.height == 0) { + return Color.clear; + } + + float u = Mathf.InverseLerp(rect.min.x, rect.max.x, point.x); + float v = Mathf.InverseLerp(rect.min.y, rect.max.y, point.y); + + return Sample(texture, u, v); + } + + /// + /// Gets the pixel color at the UV coordinates calculated from a + /// position inside a bounds. The position uses the x and z axis. + /// + /// The texture to sample from. + /// The bounds to sample from. + /// The position inside the bounds. + /// The pixel color. + public static Color Sample(this Texture2D texture, Bounds bounds, Vector3 position) + { + if (texture == null || texture.width == 0 || texture.height == 0) { + return Color.clear; + } + + float u = Mathf.InverseLerp(bounds.min.x, bounds.max.x, position.x); + float v = Mathf.InverseLerp(bounds.min.z, bounds.max.z, position.z); + + return Sample(texture, u, v); + } + + /// + /// Sets every pixel in the texture to the specified color. + /// + /// The texture to set the color of. + /// The color to set the texture to. + public static void SetColor(this Texture2D texture, Color32 color) + { + var colors = texture.GetRawTextureData(); + int length = colors.Length; + + for (int i = 0; i < length; i++) { + colors[i] = color; + } + + texture.Apply(); + } + + } + +} diff --git a/Runtime/Extensions/TextureExtensions.cs.meta b/Runtime/Extensions/TextureExtensions.cs.meta new file mode 100644 index 0000000..d978d12 --- /dev/null +++ b/Runtime/Extensions/TextureExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3ab0b023fcc831b429ae0f7b8cbd088c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 874209f51f8f0172d8d6bc3158fec67decc64652 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 17:26:43 -0500 Subject: [PATCH 37/41] Add utility class to generate procedural meshes --- Runtime/MeshGenerator.cs | 137 ++++++++++++++++++++++++++++++++++ Runtime/MeshGenerator.cs.meta | 11 +++ 2 files changed, 148 insertions(+) create mode 100644 Runtime/MeshGenerator.cs create mode 100644 Runtime/MeshGenerator.cs.meta diff --git a/Runtime/MeshGenerator.cs b/Runtime/MeshGenerator.cs new file mode 100644 index 0000000..7ee91d0 --- /dev/null +++ b/Runtime/MeshGenerator.cs @@ -0,0 +1,137 @@ +ο»Ώusing UnityEngine; +using UnityEngine.Rendering; + +namespace Zigurous.Graphics +{ + /// + /// Utility class to generate procedural meshes at runtime. + /// + public static class MeshGenerator + { + public delegate Vector3 VertexGenerator(int x, int y, float u, float v); + + /// + /// Creates a new procedural mesh of a grid of points. + /// + /// The width of the grid of points. + /// The height of the grid of points. + public static Mesh Create(int width, int height) + { + Mesh mesh = new Mesh(); + + if (width * height > 65535) { + mesh.indexFormat = IndexFormat.UInt32; + } + + mesh.vertices = CreateVertices(width, height, DefaultVertexGenerator); + mesh.triangles = CreateTriangles(width, height); + mesh.uv = CreateUVs(width, height); + mesh.RecalculateNormals(); + + return mesh; + } + + /// + /// Creates a new procedural mesh using a custom vertex function. + /// + /// The width of the grid of points. + /// The height of the grid of points. + /// A custom function to calculate the vertex for a given point. + public static Mesh Create(int width, int height, VertexGenerator vertexGenerator) + { + if (vertexGenerator == null) { + vertexGenerator = DefaultVertexGenerator; + } + + Mesh mesh = new Mesh(); + + if (width * height > 65535) { + mesh.indexFormat = IndexFormat.UInt32; + } + + mesh.vertices = CreateVertices(width, height, vertexGenerator); + mesh.triangles = CreateTriangles(width, height); + mesh.uv = CreateUVs(width, height); + mesh.RecalculateNormals(); + + return mesh; + } + + private static Vector3[] CreateVertices(int width, int height, VertexGenerator vertexGenerator) + { + Vector3[] verticies = new Vector3[(width + 1) * (height + 1)]; + + int index = 0; + + for (int y = 0; y <= height; y++) + { + for (int x = 0; x <= width; x++) + { + float u = x / (float)width; + float v = y / (float)height; + + verticies[index] = vertexGenerator(x, y, u, v); + index++; + } + } + + return verticies; + } + + private static int[] CreateTriangles(int width, int height) + { + int[] triangles = new int[width * height * 6]; + + int vert = 0; + int tris = 0; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + triangles[tris + 0] = vert + 0; + triangles[tris + 1] = vert + width + 1; + triangles[tris + 2] = vert + 1; + triangles[tris + 3] = vert + 1; + triangles[tris + 4] = vert + width + 1; + triangles[tris + 5] = vert + width + 2; + + vert++; + tris += 6; + } + + vert++; + } + + return triangles; + } + + private static Vector2[] CreateUVs(int width, int height) + { + Vector2[] uvs = new Vector2[(width + 1) * (height + 1)]; + + int index = 0; + + for (int y = 0; y <= height; y++) + { + for (int x = 0; x <= width; x++) + { + float u = x / (float)width; + float v = y / (float)height; + + uvs[index] = new Vector2(u, v); + index++; + } + } + + return uvs; + } + + private static Vector3 DefaultVertexGenerator(int x, int y, float u, float v) + { + return new Vector3(x, 0f, y); + } + + } + +} diff --git a/Runtime/MeshGenerator.cs.meta b/Runtime/MeshGenerator.cs.meta new file mode 100644 index 0000000..3664a7e --- /dev/null +++ b/Runtime/MeshGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aaeb0dc493c4e8d498132268be89d3a0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 39a09feb93344293748c8e2793daae9f7be2148f Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 17:27:05 -0500 Subject: [PATCH 38/41] Add class to split a polygon into triangles --- Runtime/Triangulator.cs | 158 +++++++++++++++++++++++++++++++++++ Runtime/Triangulator.cs.meta | 11 +++ 2 files changed, 169 insertions(+) create mode 100644 Runtime/Triangulator.cs create mode 100644 Runtime/Triangulator.cs.meta diff --git a/Runtime/Triangulator.cs b/Runtime/Triangulator.cs new file mode 100644 index 0000000..08b98d3 --- /dev/null +++ b/Runtime/Triangulator.cs @@ -0,0 +1,158 @@ +ο»Ώusing System.Collections.Generic; +using UnityEngine; + +namespace Zigurous.Graphics +{ + /// + /// Splits a polygon into triangles. + /// + public static class Triangulator + { + /// + /// Splits a polygon into triangles. Supports concave polygons but not + /// polygons with holes. + /// + /// The points that form a polygon. + /// The indices of the triangles. + public static int[] Triangulate(Vector2[] points) + { + int n = points.Length; + + if (n < 3) { + return new int[0]; + } + + int[] V = new int[n]; + + if (Area(points) > 0f) + { + for (int v = 0; v < n; v++) { + V[v] = v; + } + } + else + { + for (int v = 0; v < n; v++) { + V[v] = (n - 1) - v; + } + } + + List indices = new List(); + + int nv = n; + int count = 2 * nv; + + for (int m = 0, v = nv - 1; nv > 2;) + { + if ((count--) <= 0) { + return indices.ToArray(); + } + + int u = v; + + if (nv <= u) { + u = 0; + } + + v = u + 1; + + if (nv <= v) { + v = 0; + } + + int w = v + 1; + + if (nv <= w) { + w = 0; + } + + if (Snip(points, u, v, w, nv, V)) + { + int a, b, c, s, t; + + a = V[u]; + b = V[v]; + c = V[w]; + + indices.Add(a); + indices.Add(b); + indices.Add(c); + + m++; + + for (s = v, t = v + 1; t < nv; s++, t++) { + V[s] = V[t]; + } + + nv--; + count = 2 * nv; + } + } + + indices.Reverse(); + return indices.ToArray(); + } + + private static float Area(Vector2[] points) + { + int n = points.Length; + float A = 0f; + + for (int p = n - 1, q = 0; q < n; p = q++) + { + Vector2 pval = points[p]; + Vector2 qval = points[q]; + A += pval.x * qval.y - qval.x * pval.y; + } + + return (A * 0.5f); + } + + private static bool Snip(Vector2[] points, int u, int v, int w, int n, int[] V) + { + Vector2 A = points[V[u]]; + Vector2 B = points[V[v]]; + Vector2 C = points[V[w]]; + + if (Mathf.Epsilon > (((B.x - A.x) * (C.y - A.y)) - ((B.y - A.y) * (C.x - A.x)))) { + return false; + } + + for (int p = 0; p < n; p++) + { + if ((p == u) || (p == v) || (p == w)) { + continue; + } + + Vector2 P = points[V[p]]; + + if (InsideTriangle(A, B, C, P)) { + return false; + } + } + + return true; + } + + private static bool InsideTriangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P) + { + float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy; + float cCROSSap, bCROSScp, aCROSSbp; + + ax = C.x - B.x; ay = C.y - B.y; + bx = A.x - C.x; by = A.y - C.y; + cx = B.x - A.x; cy = B.y - A.y; + apx = P.x - A.x; apy = P.y - A.y; + bpx = P.x - B.x; bpy = P.y - B.y; + cpx = P.x - C.x; cpy = P.y - C.y; + + aCROSSbp = ax * bpy - ay * bpx; + cCROSSap = cx * apy - cy * apx; + bCROSScp = bx * cpy - by * cpx; + + return ((aCROSSbp >= 0f) && (bCROSScp >= 0f) && (cCROSSap >= 0f)); + } + + } + +} diff --git a/Runtime/Triangulator.cs.meta b/Runtime/Triangulator.cs.meta new file mode 100644 index 0000000..33afc25 --- /dev/null +++ b/Runtime/Triangulator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 61cdf4181a75e484797fd76671c72f13 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 8244d42b37c84f2e8fe1e9756de2e51750bf3ba7 Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 18:19:35 -0500 Subject: [PATCH 39/41] Formatting changes --- Runtime/Extensions/MaterialExtensions.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Runtime/Extensions/MaterialExtensions.cs b/Runtime/Extensions/MaterialExtensions.cs index e9b74db..032faf9 100644 --- a/Runtime/Extensions/MaterialExtensions.cs +++ b/Runtime/Extensions/MaterialExtensions.cs @@ -19,14 +19,10 @@ public static RenderingMode GetRenderingMode(this Material material) switch (mode) { - case 1: - return RenderingMode.Cutout; - case 2: - return RenderingMode.Fade; - case 3: - return RenderingMode.Transparent; - default: - return RenderingMode.Opaque; + case 1: return RenderingMode.Cutout; + case 2: return RenderingMode.Fade; + case 3: return RenderingMode.Transparent; + default: return RenderingMode.Opaque; } } From 79a627592582992ee64681a30521ae5ea3888fdb Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 18:23:06 -0500 Subject: [PATCH 40/41] Update documentation --- Documentation~/articles/custom-meshes.md | 6 +-- Documentation~/articles/extension-methods.md | 15 ++++++ Documentation~/articles/index.md | 8 +-- .../articles/procedural-generation.md | 38 ++++++++++++++ Documentation~/articles/shader-properties.md | 52 ------------------- Documentation~/articles/texture-drawers.md | 13 ++++- Documentation~/data/sidenav.json | 16 +++--- README.md | 5 +- Runtime/CheckerboardTextureDrawer.cs | 2 +- Runtime/CombineChildrenMeshes.cs | 5 +- Runtime/TextureDrawerRenderer.cs | 3 ++ 11 files changed, 90 insertions(+), 73 deletions(-) create mode 100644 Documentation~/articles/extension-methods.md create mode 100644 Documentation~/articles/procedural-generation.md delete mode 100644 Documentation~/articles/shader-properties.md diff --git a/Documentation~/articles/custom-meshes.md b/Documentation~/articles/custom-meshes.md index 6b5c31a..adf438f 100644 --- a/Documentation~/articles/custom-meshes.md +++ b/Documentation~/articles/custom-meshes.md @@ -11,7 +11,7 @@ The **Graphics Utils** package includes a few custom cube meshes. - `Cube-Inverted.mesh`: cube mesh with inverted normals and triangles (inside-out) - `Cube-Tiling.mesh` cube mesh designed specifically for [Material Tiling](/manual/material-tiling) -There are also 3 different scripts to generate cube meshes at runtime: +There are also 3 different scripts to generate these cube meshes at runtime: - [CubeMesh](/api/Zigurous.Graphics/CubeMesh) - [CubeMesh3](/api/Zigurous.Graphics/CubeMesh3) @@ -38,7 +38,7 @@ int[] triangles = mesh.InvertedTriangles(); ## πŸ”° Combining Meshes -The **Graphics Utils** package includes a script to combine multiple meshes into a single mesh. This can sometimes be used to improve rendering performance, or as a way to create custom meshes and turn them into assets. Add the [CombineChildrenMeshes](/api/Zigurous.Graphics/CombineChildrenMeshes) script to a game object that includes an empty mesh filter. The script will combine the meshes of the children objects and apply the new combined mesh to the parent mesh filter. +The **Graphics Utils** package includes a script to combine multiple meshes into a single mesh. This can be used to improve rendering performance, or as a way to create custom meshes and turn them into assets. Add the [CombineChildrenMeshes](/api/Zigurous.Graphics/CombineChildrenMeshes) script to the parent game object of children meshes. The combined mesh will be assigned to the mesh filter of the parent object, and the child game objects will either be destroyed or disabled. You can also manually combine meshes through an extension method: @@ -51,7 +51,7 @@ Mesh combinedMesh = filters.CombineMesh(); ## πŸ’Ύ Saving Meshes -Often when generating meshes at runtime, you may want to save that mesh as an asset for future use so you do not need to regenerate them over and over. The **Graphics Utils** package comes with a [SaveMesh](/api/Zigurous.Graphics/SaveMesh) script that will save a mesh as an asset at runtime. +Often when generating meshes at runtime, you may want to save that mesh as an asset for future use so you don't need to regenerate them again. The **Graphics Utils** package comes with a [SaveMesh](/api/Zigurous.Graphics/SaveMesh) script that will save a mesh as an asset at runtime. You can also manually save a mesh through extension methods: diff --git a/Documentation~/articles/extension-methods.md b/Documentation~/articles/extension-methods.md new file mode 100644 index 0000000..2ebbc17 --- /dev/null +++ b/Documentation~/articles/extension-methods.md @@ -0,0 +1,15 @@ +--- +slug: "/manual/extension-methods" +--- + +# Extension Methods + +The **Graphics Utils** package contains dozens of extension methods to provide enhanced support for common Unity classes. See each Scripting API for more information: + +#### [Material](/api/Zigurous.Architecture/MaterialExtensions) + +#### [Mesh](/api/Zigurous.Architecture/MeshExtensions) + +#### [MeshFilter](/api/Zigurous.Architecture/MeshFilterExtensions) + +#### [Texture](/api/Zigurous.Architecture/TextureExtensions) diff --git a/Documentation~/articles/index.md b/Documentation~/articles/index.md index f0e5026..4264f24 100644 --- a/Documentation~/articles/index.md +++ b/Documentation~/articles/index.md @@ -22,10 +22,12 @@ The **Graphics Utils** package provides scripts and utilities for graphics and r ## Reference -#### πŸ›€οΈ [Material Tiling](/manual/material-tiling) - #### πŸ”° [Custom Meshes](/manual/custom-meshes) -#### 🌌 [Shader Properties](/manual/shader-properties) +#### ⛰️ [Procedural Generation](/manual/procedural-generation) + +#### πŸ›€οΈ [Material Tiling](/manual/material-tiling) #### πŸ–ΌοΈ [Texture Drawers](/manual/texture-drawers) + +#### πŸ”Œ [Extension Methods](/manual/extension-methods) diff --git a/Documentation~/articles/procedural-generation.md b/Documentation~/articles/procedural-generation.md new file mode 100644 index 0000000..c4a173f --- /dev/null +++ b/Documentation~/articles/procedural-generation.md @@ -0,0 +1,38 @@ +--- +slug: "/manual/procedural-generation" +--- + +# Procedural Generation + +The **Graphics Utils** package provides a utility class to generate procedural meshes at runtime. For example, use the [MeshGenerator](/api/Zigurous.Graphics/MeshGenerator) class to create a new procedural mesh that forms a grid of points. + +```csharp +Mesh mesh = MeshGenerator.Create(64, 64); +``` + +This is a useful starting point to create more complex meshes, such as procedural terrain. The [MeshGenerator](/api/Zigurous.Graphics/MeshGenerator) class allows you to provide your own custom vertex generation function to create these more complex meshes. + +```csharp +void GenerateTerrain() +{ + Mesh terrain = MeshGenerator.Create(64, 64, VertexGenerator); +} + +void VertexGenerator(int x, int y, float u, float v) +{ + // sample terrain height using a noise function + float height = Mathf.PerlinNoise(x, y); + return new Vector3(x, height, y); +} +``` + +
+ +## πŸ’Ž Triangulation + +Sometimes it is useful to split a polygon into triangles in order to generate a custom mesh for the polygon. The [Triangulator](/api/Zigurous.Graphics/Triangulator) class provides this utility. Pass in the points that form the polygon, and the indices of the triangles will be returned. + +```csharp +Vector2[] polygon; // array of points that form a polygon +int[] triangles = Triangulator.Triangulate(polygon); +``` diff --git a/Documentation~/articles/shader-properties.md b/Documentation~/articles/shader-properties.md deleted file mode 100644 index 17366fc..0000000 --- a/Documentation~/articles/shader-properties.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -slug: "/manual/shader-properties" ---- - -# Shader Properties - -When setting shader properties on materials, it is more efficient to use property ids instead of strings. The **Graphics Utils** package comes with a static class [Identifier](/api/Zigurous.Graphics/Identifier) with predefined ids for common shader properties. - -```csharp -private void Start() -{ - Material material = GetComponent().material; - material.SetFloat(Identifier.Glossiness, 1f); -} -``` - -
- -## πŸ”– Automatic Property Ids - -The [ShaderProperty](/api/Zigurous.Graphics/ShaderProperty) struct included in the **Graphics Utils** package automatically creates a property id for a given shader property name. Anywhere you might declare a variable for a custom shader property name use `ShaderProperty` instead. It will still be serialized as a string in the editor, but you can use the id when getting or setting a shader property on a material. - -```csharp -public ShaderProperty property = "_Custom"; - -private void Start() -{ - Material material = GetComponent().material; - material.SetFloat(property.id, 1f); -} -``` - -
- -## 🌠 Animated Properties - -Sometimes you want to animate a shader property over time. The **Graphics Utils** package includes a few data structures to accomplish this. You can declare any of the following: - -- [AnimatedShaderIntProperty](/api/Zigurous.Graphics/AnimatedShaderIntProperty) -- [AnimatedShaderFloatProperty](/api/Zigurous.Graphics/AnimatedShaderFloatProperty) -- [AnimatedShaderColorProperty](/api/Zigurous.Graphics/AnimatedShaderColorProperty) - -These structs provide properties to set the animated values (usually done in the editor). At this point your script can call `Animate(material, time)` to evaluate the value at the given time and apply it to the provided material. - -```csharp -public AnimatedShaderFloatProperty property; - -private void Update() -{ - property.Animate(material, time); -} -``` diff --git a/Documentation~/articles/texture-drawers.md b/Documentation~/articles/texture-drawers.md index 93c6128..40c62d7 100644 --- a/Documentation~/articles/texture-drawers.md +++ b/Documentation~/articles/texture-drawers.md @@ -4,9 +4,14 @@ slug: "/manual/texture-drawers" # Texture Drawers -The **Graphics Utils** package includes a base class for drawing textures at runtime. It provides the boilerplate code for creating and drawing new textures programmatically. Create a new class that inherits from [TextureDrawer](/api/Zigurous.Graphics/TextureDrawer) and override the function `SetPixels(Texture2D)` to complete the implementation. +The **Graphics Utils** package includes a base class for drawing textures at runtime. It provides the boilerplate code for creating and drawing new textures programmatically. + +Create a new class that inherits from [TextureDrawer](/api/Zigurous.Graphics/TextureDrawer) and override the function `SetPixels(Texture2D)` to complete the implementation. + +Since [TextureDrawer](/api/Zigurous.Graphics/TextureDrawer) is a ScriptableObject, you'll want to add the `[CreateAssetMenu]` attribute to your class so you can save an instance of the class as an asset in your project using Unity's asset menu. ```csharp +[CreateAssetMenu] public class CustomTextureDrawer : TextureDrawer { public override void SetPixels(Texture2D texture) @@ -20,10 +25,14 @@ public class CustomTextureDrawer : TextureDrawer ## πŸ–ΌοΈ Rendering -The [TextureDrawer](/api/Zigurous.Graphics/TextureDrawer) script will automatically apply your texture to the renderer's main material on the object if a Renderer component is available. This is not required, but often is useful. There are additional customization options available in regards to the rendering, such as which shader property the texture is set to. +The **Graphics Utils** package comes with a script to quickly render the result of a [TextureDrawer](/api/Zigurous.Graphics/TextureDrawer). This is an optional, but useful script to preview the texture without having to write any other code to manually assign the texture to a material. + +Add the [TextureDrawerRenderer](/api/Zigurous.Graphics/TextureDrawerRenderer) script to any game object that contains any type of Renderer component. Assign the texture drawer you want to use and customize any other options you'd like. You can even render the texture in the editor without having to run the game.
## 🏁 Checkerboard The **Graphics Utils** package includes a script [CheckerboardTextureDrawer](/api/Zigurous.Graphics/CheckerboardTextureDrawer) as a sample implementation. It is, of course, a fully functional script that draws a checkerboard pattern. This can be used however you desire, and there are even a number of customization options available. + +To create a new checkerboard pattern texture, use the asset menu `Create > Zigurous > Graphics > Checkerboard Texture Drawer`. diff --git a/Documentation~/data/sidenav.json b/Documentation~/data/sidenav.json index 4862a52..a7cfdd1 100644 --- a/Documentation~/data/sidenav.json +++ b/Documentation~/data/sidenav.json @@ -23,21 +23,25 @@ { "title": "πŸ“– Reference", "items": [ - { - "name": "Material Tiling", - "path": "/manual/material-tiling" - }, { "name": "Custom Meshes", "path": "/manual/custom-meshes" }, { - "name": "Shader Properties", - "path": "/manual/shader-properties" + "name": "Procedural Generation", + "path": "/manual/procedural-generation" + }, + { + "name": "Material Tiling", + "path": "/manual/material-tiling" }, { "name": "Texture Drawers", "path": "/manual/texture-drawers" + }, + { + "name": "Extension Methods", + "path": "/manual/extension-methods" } ] }, diff --git a/README.md b/README.md index fb31b3a..527b510 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,11 @@ The **Graphics Utils** package provides scripts and utilities for graphics and r ## Reference -- [Auto Tiling](https://docs.zigurous.com/com.zigurous.graphics/manual/material-tiling) - [Custom Meshes](https://docs.zigurous.com/com.zigurous.graphics/manual/custom-meshes) -- [Shader Properties](https://docs.zigurous.com/com.zigurous.graphics/manual/shader-properties) +- [Procedural Generation](https://docs.zigurous.com/com.zigurous.graphics/manual/procedural-generation) +- [Material Tiling](https://docs.zigurous.com/com.zigurous.graphics/manual/material-tiling) - [Texture Drawers](https://docs.zigurous.com/com.zigurous.graphics/manual/texture-drawers) +- [Extension Methods](https://docs.zigurous.com/com.zigurous.graphics/manual/extension-methods) ## Installation diff --git a/Runtime/CheckerboardTextureDrawer.cs b/Runtime/CheckerboardTextureDrawer.cs index 5e6099f..13dd385 100644 --- a/Runtime/CheckerboardTextureDrawer.cs +++ b/Runtime/CheckerboardTextureDrawer.cs @@ -3,7 +3,7 @@ namespace Zigurous.Graphics { /// - /// Draws a texture of a checkerboard pattern. + /// Draws a checkerboard pattern texture. /// [CreateAssetMenu(menuName = "Zigurous/Graphics/Checkerboard Texture Drawer")] [HelpURL("https://docs.zigurous.com/com.zigurous.graphics/api/Zigurous.Graphics/CheckerboardTextureDrawer")] diff --git a/Runtime/CombineChildrenMeshes.cs b/Runtime/CombineChildrenMeshes.cs index 9db9eaa..5cb5fc7 100644 --- a/Runtime/CombineChildrenMeshes.cs +++ b/Runtime/CombineChildrenMeshes.cs @@ -3,7 +3,7 @@ namespace Zigurous.Graphics { /// - /// Combines the meshes of the children of the game object into one mesh. + /// Combines children meshes into one mesh. /// [AddComponentMenu("Zigurous/Graphics/Combine Children Meshes")] [HelpURL("https://docs.zigurous.com/com.zigurous.graphics/api/Zigurous.Graphics/CombineChildrenMeshes")] @@ -79,13 +79,11 @@ public Mesh Combine() continue; } - // Create a mesh combine instance CombineInstance instance = new CombineInstance(); instance.mesh = child.mesh; instance.transform = child.transform.localToWorldMatrix; combine[submesh++] = instance; - // Destroy the child mesh if (child.transform != this.transform) { if (deleteChildren) { @@ -96,7 +94,6 @@ public Mesh Combine() } } - // Create a new mesh from all of the combined children Mesh combinedMesh = new Mesh(); combinedMesh.name = combinedMeshName; combinedMesh.CombineMeshes(combine, mergeSubmeshes); diff --git a/Runtime/TextureDrawerRenderer.cs b/Runtime/TextureDrawerRenderer.cs index 56bc82d..18fdbb5 100644 --- a/Runtime/TextureDrawerRenderer.cs +++ b/Runtime/TextureDrawerRenderer.cs @@ -3,6 +3,9 @@ namespace Zigurous.Graphics { + /// + /// Renders the result of a . + /// [ExecuteAlways] [AddComponentMenu("Zigurous/Graphics/Texture Drawer Renderer")] [HelpURL("https://docs.zigurous.com/com.zigurous.graphics/api/Zigurous.Graphics/TextureDrawerRenderer")] From 71a3e72bee3e9486b211b9cdcbae97b46736b7ab Mon Sep 17 00:00:00 2001 From: Adam Graham Date: Mon, 19 Jun 2023 18:23:12 -0500 Subject: [PATCH 41/41] Update 0.4.0 changelog --- CHANGELOG.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d136bd8..1070fdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,34 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.4.0] - 2022/10/30 +## [0.4.0] - 2023/06/19 ### Added +- New `MeshGenerator` static class to generate procedural meshes +- New `Triangulator` static class to split polygons into triangles - New `HiddenMaterialPropertyDrawer` attribute to hide material properties -- Help URLs added to behaviors +- New `Mesh.RecalculateUV` extension method +- New texture extension methods + - `GetPixelCoordinates` + - `GetUVCoordinates` + - `Sample(u, v)` + - `Sample(rect, point)` + - `Sample(bounds, position)` + - `SetColor` +- Context menu added to `SaveMesh` to save directly from the editor +- Help URLs added to all behaviors + +### Changed + +- Refactored `TextureDrawer` as a ScriptableObject and a separate `TextureDrawerRenderer` behavior +- Improved `CombineChildrenMeshes` with better transform matrix, option to set mesh name, and toggle to destroy or disable child game objects +- Renamed `AutoTile.Submesh` to `AutoTile.SubmeshTiling` +- Formatting changes + +### Removed + +- `ShaderProperty` and `AnimatedShaderProperty` (moved to AnimationLibrary package) ## [0.3.0] - 2021/11/14