新增动态水物理插件

This commit is contained in:
Bob.Song
2026-02-27 17:44:21 +08:00
parent a6e061d9ce
commit 60744d113d
2218 changed files with 698551 additions and 189 deletions

View File

@@ -0,0 +1,73 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using NWH.DWP2.WaterObjects;
using UnityEngine;
#endregion
namespace NWH.DWP2.DemoContent
{
/// <summary>
/// Demo script that spawns a 3D grid of cubes with WaterObjects attached.
/// Used for stress-testing and demonstrating the buoyancy system with multiple objects.
/// </summary>
public class CubeGridSpawner : MonoBehaviour
{
/// <summary>
/// Spacing between cubes on the Z axis.
/// </summary>
public float depth = 1.1f;
/// <summary>
/// Spacing between cubes on the Y axis.
/// </summary>
public float height = 1.1f;
/// <summary>
/// Spacing between cubes on the X axis.
/// </summary>
public float width = 1.1f;
/// <summary>
/// Number of cubes to spawn along the X axis.
/// </summary>
public int xResolution = 10;
/// <summary>
/// Number of cubes to spawn along the Y axis.
/// </summary>
public int yResolution = 10;
/// <summary>
/// Number of cubes to spawn along the Z axis.
/// </summary>
public int zResolution = 10;
private void Start()
{
for (int x = 0; x < xResolution; x++)
{
for (int y = 0; y < yResolution; y++)
{
for (int z = 0; z < zResolution; z++)
{
GameObject spawnedObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
spawnedObject.transform.position = new Vector3(x * width, y * height, z * depth);
Rigidbody rb = spawnedObject.AddComponent<Rigidbody>();
rb.mass = 200f;
spawnedObject.AddComponent<WaterObject>();
}
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d5de9b2339abbff43b28c94b9c1fb517
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a647402bad23c2e4eb3c4eb8efff52a8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,56 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#if UNITY_EDITOR
#region
using NWH.NUI;
using NWH.DWP2.WaterObjects;
using UnityEditor;
#endregion
namespace NWH.DWP2
{
/// <summary>
/// Custom inspector for MassFromChildren.
/// </summary>
[CustomEditor(typeof(MassFromChildren))]
[CanEditMultipleObjects]
public class MassFromChildrenEditor : DWP2NUIEditor
{
private MassFromChildren _massFromChildren;
/// <summary>
/// Draws custom inspector GUI for MassFromChildren.
/// </summary>
public override bool OnInspectorNUI()
{
_massFromChildren = (MassFromChildren)target;
if (!base.OnInspectorNUI() || _massFromChildren == null)
{
return false;
}
drawer.Info("Sums mass of all 'MassFromVolume's attached to this and child objects.");
if (drawer.Button("Calculate Mass From Children"))
{
_massFromChildren.Calculate();
}
drawer.EndEditor(this);
return true;
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6e2a38dc6b814caa8f2638c49a2972c7
timeCreated: 1593013502

View File

@@ -0,0 +1,84 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#if UNITY_EDITOR
#region
using NWH.NUI;
using NWH.DWP2.WaterObjects;
using UnityEditor;
#endregion
namespace NWH.DWP2
{
/// <summary>
/// Custom inspector for MassFromVolume.
/// </summary>
[CustomEditor(typeof(MassFromVolume))]
[CanEditMultipleObjects]
public class MassFromVolumeEditor : DWP2NUIEditor
{
private MassFromVolume _massFromVolume;
public void OnEnable()
{
_massFromVolume = (MassFromVolume)target;
}
/// <summary>
/// Draws custom inspector GUI for MassFromVolume.
/// </summary>
public override bool OnInspectorNUI()
{
if (!base.OnInspectorNUI() || _massFromVolume == null)
{
return false;
}
_massFromVolume = (MassFromVolume)target;
// Material settings
drawer.Field("mass", true, "kg");
drawer.Field("volume", false, "m3");
drawer.Info("Volume is auto-calculated from the mesh when either Calculate option is used.");
drawer.BeginSubsection("Density");
drawer.Field("density", true, "kg/m3");
if (drawer.Button("Calculate Mass From Density"))
{
foreach (MassFromVolume mfm in targets)
{
mfm.CalculateAndApplyFromDensity(mfm.density);
}
}
drawer.EndSubsection();
drawer.BeginSubsection("Material");
drawer.Field("material");
if (drawer.Button("Calculate Mass From Material"))
{
foreach (MassFromVolume mfm in targets)
{
mfm.CalculateAndApplyFromMaterial();
}
}
drawer.EndSubsection();
drawer.EndEditor(this);
return true;
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e4d7cea0eaeb4875948ee3242c6d65fe
timeCreated: 1593873668

View File

@@ -0,0 +1,365 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#if UNITY_EDITOR
#region
using NWH.NUI;
using NWH.DWP2.WaterObjects;
using UnityEditor;
using UnityEngine;
#endregion
namespace NWH.DWP2
{
/// <summary>
/// Custom inspector for WaterObject.
/// </summary>
[CustomEditor(typeof(WaterObject))]
[CanEditMultipleObjects]
[ExecuteAlways]
public class WaterObjectEditor : DWP2NUIEditor
{
private bool _editorHasWarnings;
private Texture2D _greyTexture;
private Texture2D _originalMeshPreviewTexture;
private float _previewHeight;
private Texture2D _simMeshPreviewTexture;
private WaterObject _waterObject;
private void OnEnable()
{
_greyTexture = new Texture2D(10, 10);
FillInTexture(ref _greyTexture, new Color32(66, 66, 66, 255));
}
private static void FillInTexture(ref Texture2D tex, Color color)
{
Color[] fillColorArray = tex.GetPixels();
for (int i = 0; i < fillColorArray.Length; i++)
{
fillColorArray[i] = color;
}
tex.SetPixels(fillColorArray);
tex.Apply();
}
/// <summary>
/// Draws custom inspector GUI for WaterObject.
/// </summary>
public override bool OnInspectorNUI()
{
if (!base.OnInspectorNUI())
{
return false;
}
// Get water object and make sure it is initialized
_waterObject = (WaterObject)target;
// Draw logo texture
Rect logoRect = drawer.positionRect;
logoRect.height = 60f;
drawer.DrawEditorTexture(logoRect, "Dynamic Water Physics 2/Logos/WaterObjectLogo");
drawer.AdvancePosition(logoRect.height);
if (_waterObject.originalMesh == null || _waterObject.serializedSimulationMesh == null ||
_waterObject.SimulationMesh == null)
{
drawer.Space();
drawer.Info("Simulation mesh not generated. Click 'Update Simulation Mesh' button below to fix this.",
MessageType.Error);
}
if (_waterObject.CurrentWaterDataProvider)
{
drawer.Info($"Current water data source: '{_waterObject.CurrentWaterDataProvider.GetType().Name}' " +
$"on object '{_waterObject.CurrentWaterDataProvider.name}'.");
}
if (Application.isPlaying)
{
drawer.Info($"Forces are being applied to '{_waterObject.targetRigidbody}'.");
}
drawer.BeginSubsection("Buoyancy");
if (Application.isPlaying)
{
drawer.Field("submergedVolume", false, "l");
}
drawer.Field("buoyantForceCoefficient");
drawer.Field("fluidDensity");
drawer.EndSubsection();
drawer.BeginSubsection("Hydrodynamics");
drawer.Field("hydrodynamicForceCoefficient");
drawer.Field("slamForceCoefficient");
drawer.Field("suctionForceCoefficient");
drawer.Field("skinDragCoefficient");
drawer.Field("velocityDotPower");
drawer.EndSubsection();
drawer.BeginSubsection("Water");
drawer.Field("calculateWaterHeights");
drawer.Field("calculateWaterNormals");
drawer.Field("calculateWaterFlows");
drawer.Field("defaultWaterHeight");
drawer.Field("defaultWaterNormal");
drawer.Field("defaultWaterFlow");
drawer.EndSubsection();
// Simulation mesh
drawer.BeginSubsection("Simulation Mesh Settings");
if (drawer.Field("simplifyMesh").boolValue)
{
drawer.Field("targetTriangleCount");
}
drawer.Field("convexifyMesh");
drawer.Field("weldColocatedVertices");
if (drawer.Button("Update Simulation Mesh"))
{
UpdateSimulationMesh();
}
if (drawer.Button("Toggle In-Scene Preview"))
{
ToggleInScenePreview();
}
if (targets.Length == 1)
{
drawer.Label("Simulation Mesh Preview:");
drawer.Space();
if (Event.current.type == EventType.Repaint)
{
DrawPreviewTexture(_waterObject, drawer.positionRect, out _previewHeight);
}
drawer.Space(_previewHeight);
}
drawer.EndSubsection();
// Warnings
drawer.BeginSubsection("Messages");
DrawWarnings();
drawer.EndSubsection();
drawer.EndEditor(this);
return true;
}
/// <summary>
/// Draws validation warnings and errors.
/// </summary>
private void DrawWarnings()
{
if (targets.Length == 1)
{
_editorHasWarnings = false;
// Missing rigidbody
if (_waterObject.targetRigidbody == null)
{
_waterObject.targetRigidbody = _waterObject.transform.GetComponentInParent<Rigidbody>(true);
if (_waterObject.targetRigidbody == null)
{
drawer.Info("WaterObject requires a rigidbody attached to the object or its parent(s) to " +
$"function. Add a rigidbody to object {_waterObject.name} or one of its parents.",
MessageType.Error);
_editorHasWarnings = true;
if (drawer.Button("Add a Rigidbody"))
{
foreach (WaterObject wo in targets)
{
wo.gameObject.AddComponent<Rigidbody>();
}
}
}
}
// Collider count
if (_waterObject.targetRigidbody != null)
{
int colliderCount = _waterObject.targetRigidbody.transform.GetComponentsInChildren<Collider>()
.Length;
if (_waterObject.targetRigidbody.transform.GetComponentsInChildren<Collider>().Length == 0)
{
drawer.Info(
$"Found {colliderCount} colliders attached to rigidbody {_waterObject.targetRigidbody.name} " +
"and its children. At least one collider is required for a rigidbody to work properly.",
MessageType.Error);
_editorHasWarnings = true;
if (drawer.Button("Add a MeshCollider"))
{
foreach (WaterObject wo in targets)
{
MeshCollider mc = wo.gameObject.AddComponent<MeshCollider>();
mc.convex = true;
mc.isTrigger = false;
}
}
}
}
// Excessive triangle count
if (_waterObject.triangleCount > 150)
{
drawer.Info($"Possible excessive number of triangles detected ({_waterObject.triangleCount})." +
" Use simplify mesh option to reduce the number of triangles, or if this is intentional ignore this message." +
" Recommended number is 16-128.", MessageType.Warning);
}
// Scale error
if (_waterObject.transform.localScale.x <= 0
|| _waterObject.transform.localScale.y <= 0
|| _waterObject.transform.localScale.z <= 0)
{
drawer.Info(
"Scale of this object is negative or zero on one or more of axes. Scale less than or equal to zero is not supported." +
" WaterObject will still be calculated but with unpredictable results. ", MessageType.Error);
}
if (!_editorHasWarnings)
{
drawer.Info($"No warnings or errors for object {_waterObject.name}.");
}
}
}
/// <summary>
/// Regenerates simulation mesh for selected objects.
/// </summary>
private void UpdateSimulationMesh()
{
foreach (WaterObject wo in targets)
{
wo.StopSimMeshPreview();
wo.GenerateSimMesh();
Undo.RecordObject(wo, "Updated Simulation Mesh");
EditorUtility.SetDirty(wo);
}
}
/// <summary>
/// Toggles in-scene preview of simulation mesh.
/// </summary>
private void ToggleInScenePreview()
{
foreach (WaterObject wo in targets)
{
if (wo.PreviewEnabled)
{
wo.StopSimMeshPreview();
}
else
{
wo.StartSimMeshPreview();
}
}
}
/// <summary>
/// Draws side-by-side preview of original and simulation meshes.
/// </summary>
private void DrawPreviewTexture(WaterObject waterObject, Rect rect, out float previewHeight)
{
previewHeight = 0;
if (waterObject == null)
{
return;
}
if (waterObject.originalMesh == null)
{
return;
}
if (waterObject.serializedSimulationMesh.vertices == null)
{
return;
}
if (waterObject.SimulationMesh == null)
{
return;
}
// Tri count
int originalTriCount = waterObject.originalMesh == null ? 0 : waterObject.originalMesh.triangles.Length / 3;
int originalVertCount = waterObject.originalMesh == null ? 0 : waterObject.originalMesh.vertices.Length;
int simulationTriCount = waterObject.serializedSimulationMesh.triangles.Length / 3;
int simulationVertCount = waterObject.serializedSimulationMesh.vertices.Length;
_originalMeshPreviewTexture = AssetPreview.GetAssetPreview(waterObject.originalMesh);
_simMeshPreviewTexture = waterObject.SimulationMesh == null
? AssetPreview.GetAssetPreview(waterObject.originalMesh)
: AssetPreview.GetAssetPreview(waterObject.SimulationMesh);
float startY = rect.y;
float previewWidth = rect.width;
float maxPreviewWidth = 480f;
previewWidth = Mathf.Clamp(previewWidth, 240f, maxPreviewWidth);
float margin = (rect.width - previewWidth) * 0.5f;
float halfWidth = previewWidth * 0.5f;
Rect leftRect = new(rect.x + margin, startY, halfWidth, halfWidth);
Rect rightRect = new(rect.x + halfWidth + margin, startY, halfWidth, halfWidth);
Material previewMaterial = new(Shader.Find("UI/Default"));
GUI.DrawTexture(leftRect, _originalMeshPreviewTexture == null ? _greyTexture : _originalMeshPreviewTexture);
GUI.DrawTexture(rightRect, _simMeshPreviewTexture == null ? _greyTexture : _simMeshPreviewTexture);
GUIStyle centeredStyle = GUI.skin.GetStyle("Label");
centeredStyle.alignment = TextAnchor.MiddleCenter;
centeredStyle.normal.textColor = Color.white;
Rect leftLabelRect = leftRect;
leftLabelRect.height = 20f;
GUI.Label(leftLabelRect, "ORIGINAL", centeredStyle);
Rect rightLabelRect = rightRect;
rightLabelRect.height = 20f;
GUI.Label(rightLabelRect, "SIMULATION", centeredStyle);
Rect leftBottomLabelRect = leftRect;
leftBottomLabelRect.y = leftRect.y + halfWidth - 20f;
leftBottomLabelRect.height = 20f;
GUI.Label(leftBottomLabelRect, $"{originalTriCount} tris, {originalVertCount} verts");
Rect rightBottomLabelrect = rightRect;
rightBottomLabelrect.y = rightRect.y + halfWidth - 20f;
rightBottomLabelrect.height = 20f;
GUI.Label(rightBottomLabelrect, $"{simulationTriCount} tris, {simulationVertCount} verts");
previewHeight = halfWidth;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 02a48d5b199a504459439d000ff549c1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -0,0 +1,88 @@
fileFormatVersion: 2
guid: 93c55739f324f5540b9c04732218a774
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 9
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: -1
mipBias: -100
wrapU: -1
wrapV: -1
wrapW: -1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 2
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
vertices: []
indices:
edges: []
weights: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@@ -0,0 +1,88 @@
fileFormatVersion: 2
guid: ff137dafe58a6324c908e3cb796b45b9
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 9
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: -1
mipBias: -100
wrapU: -1
wrapV: -1
wrapW: -1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 2
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
vertices: []
indices:
edges: []
weights: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,88 @@
fileFormatVersion: 2
guid: eeffff81a7107a9498f73a9ac807c6e8
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 9
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: -1
mipBias: -100
wrapU: -1
wrapV: -1
wrapW: -1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 2
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
vertices: []
indices:
edges: []
weights: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,59 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using NWH.Common.CoM;
using UnityEngine;
#endregion
namespace NWH.DWP2.WaterObjects
{
/// <summary>
/// Calculates mass of a Rigidbody from the children that have MassFromVolume script attached.
/// </summary>
[RequireComponent(typeof(Rigidbody))]
public class MassFromChildren : MonoBehaviour
{
private Rigidbody _rb;
private string _result;
/// <summary>
/// Calculates the total mass from all child MassFromVolume components and applies it to the Rigidbody.
/// Also updates the VariableCenterOfMass component if present.
/// </summary>
public void Calculate()
{
_rb = GetComponent<Rigidbody>();
float massSum = 0;
_result = "Calculated mass from: ";
foreach (MassFromVolume mam in GetComponentsInChildren<MassFromVolume>())
{
massSum += mam.mass;
_result += $"{mam.name} ({mam.mass})";
}
_result += $". Total mass: {massSum}.";
Debug.Log(_result);
if (massSum > 0.001f)
{
_rb.mass = massSum;
VariableCenterOfMass vcom = GetComponent<VariableCenterOfMass>();
if (vcom != null)
{
vcom.baseMass = massSum;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a52b52ef952dc2147bf915776311b00b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,139 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using UnityEngine;
#endregion
namespace NWH.DWP2.WaterObjects
{
/// <summary>
/// Script to calculate mass from mesh volume and material density.
/// Removes need for guessing mass and ensures that all objects of same density are submerged equally.
/// </summary>
[RequireComponent(typeof(WaterObject))]
public class MassFromVolume : MonoBehaviour
{
/// <summary>
/// Calculated density of the material in kg/m³.
/// </summary>
public float density;
/// <summary>
/// Calculated mass of the object in kg.
/// </summary>
public float mass;
/// <summary>
/// Material preset containing density value.
/// </summary>
public WaterObjectMaterial material;
/// <summary>
/// Calculated volume of the simulation mesh in m³.
/// </summary>
public float volume;
private WaterObject _waterObject;
private void Awake()
{
_waterObject = GetComponent<WaterObject>();
if (_waterObject == null)
{
Debug.LogError("MassFromVolume requires WaterObject.");
}
if (material == null)
{
SetDefaultAsMaterial();
}
}
/// <summary>
/// Sets the default WaterObjectMaterial from Resources.
/// </summary>
public void SetDefaultAsMaterial()
{
material = Resources.Load<WaterObjectMaterial>("Dynamic Water Physics 2/DefaultWaterObjectMaterial");
}
private void Reset()
{
SetDefaultAsMaterial();
}
/// <summary>
/// Gets volume of the simulation mesh. Scale-sensitive.
/// </summary>
public void CalculateSimulationMeshVolume()
{
if (_waterObject == null)
{
_waterObject = GetComponent<WaterObject>();
}
if (_waterObject.SimulationMesh == null)
{
Debug.LogWarning(
"No simulation mesh assigned/generated. Make sure that simulation mesh of WaterObject is not empty - " +
"if this is the first time setup try clicking 'Update Simulation Mesh' on WaterObject.");
}
volume = _waterObject.SimulationMesh == null
? 0.00000001f
: Mathf.Clamp(MeshUtility.VolumeOfMesh(_waterObject.SimulationMesh, _waterObject.transform),
0f, Mathf.Infinity);
}
/// <summary>
/// Calculates mass from the assigned material's density and applies it to the Rigidbody.
/// </summary>
/// <returns>Calculated mass value.</returns>
public float CalculateAndApplyFromMaterial()
{
return CalculateAndApplyFromDensity(material.density);
}
/// <summary>
/// Calculates mass from the given density and applies it to the Rigidbody.
/// </summary>
/// <param name="density">Material density in kg/m³.</param>
/// <returns>Calculated mass value.</returns>
public float CalculateAndApplyFromDensity(float density)
{
if (material != null)
{
if (_waterObject == null)
{
_waterObject = GetComponent<WaterObject>();
}
CalculateSimulationMeshVolume();
mass = density * volume;
if (_waterObject.targetRigidbody != null && mass > 0)
{
_waterObject.targetRigidbody.mass = mass;
}
return mass;
}
return -1;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5ca3428d119d4dc5a018840803e81994
timeCreated: 1593873585

View File

@@ -0,0 +1,233 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using System.Collections.Generic;
using System.Linq;
using NWH.DWP2.MeshDecimation;
using NWH.DWP2.MiConvexHull;
using UnityEngine;
#endregion
namespace NWH.DWP2.WaterObjects
{
/// <summary>
/// Utility class for mesh processing operations used by WaterObject.
/// Handles mesh simplification, convexification, vertex welding, and volume calculations.
/// </summary>
public static class MeshUtility
{
/// <summary>
/// Generates a simulation mesh from the original mesh with optional processing steps.
/// Processes the mesh according to the specified flags and simplification ratio.
/// </summary>
/// <param name="originalMesh">Source mesh to process.</param>
/// <param name="simMesh">Output simulation mesh.</param>
/// <param name="simplifyMesh">Should the mesh be simplified to reduce triangle count.</param>
/// <param name="convexifyMesh">Should the mesh be made convex.</param>
/// <param name="weldColocatedVertices">Should vertices at the same position be merged.</param>
/// <param name="simplificationRatio">Target triangle count as ratio of original (0-1).</param>
public static void GenerateSimMesh(ref Mesh originalMesh, ref Mesh simMesh,
bool simplifyMesh = false, bool convexifyMesh = false, bool weldColocatedVertices = true,
float simplificationRatio = 1f)
{
simMesh.vertices = originalMesh.vertices;
simMesh.triangles = originalMesh.triangles;
if (simplifyMesh)
{
simMesh = GenerateSimplifiedMesh(ref originalMesh, ref simMesh, simplificationRatio);
}
if (convexifyMesh)
{
simMesh = GenerateConvexMesh(simMesh);
}
if (weldColocatedVertices)
{
WeldVertices(ref simMesh);
}
simMesh.name = "DWP_SIM_MESH";
simMesh.RecalculateNormals();
simMesh.RecalculateTangents();
}
/// <summary>
/// Generate mesh from vertices and triangles.
/// </summary>
/// <param name="vertices"> Array of vertices. </param>
/// <param name="triangles"> Array of triangles (indices). </param>
/// <returns> </returns>
public static Mesh GenerateMesh(Vector3[] vertices, int[] triangles)
{
Mesh m = new();
m.vertices = vertices;
m.triangles = triangles;
m.RecalculateBounds();
m.RecalculateNormals();
m.name = "DWP_SIM_MESH";
return m;
}
/// <summary>
/// Merges vertices that are closer than the specified distance threshold.
/// Improves performance by reducing vertex count and removing duplicates.
/// </summary>
/// <param name="aMesh">Mesh to process.</param>
/// <param name="aMaxDelta">Maximum distance between vertices to be considered duplicates.</param>
public static void WeldVertices(ref Mesh aMesh, float aMaxDelta = 0.001f)
{
Vector3[] verts = aMesh.vertices;
List<int> newVerts = new();
int[] map = new int[verts.Length];
// create mapping and filter duplicates.
for (int i = 0; i < verts.Length; i++)
{
Vector3 p = verts[i];
bool duplicate = false;
for (int i2 = 0; i2 < newVerts.Count; i2++)
{
int a = newVerts[i2];
if ((verts[a] - p).sqrMagnitude <= aMaxDelta)
{
map[i] = i2;
duplicate = true;
break;
}
}
if (!duplicate)
{
map[i] = newVerts.Count;
newVerts.Add(i);
}
}
Vector3[] verts2 = new Vector3[newVerts.Count];
for (int i = 0; i < newVerts.Count; i++)
{
int a = newVerts[i];
verts2[i] = verts[a];
}
int[] tris = aMesh.triangles;
for (int i = 0; i < tris.Length; i++)
{
tris[i] = map[tris[i]];
}
aMesh.triangles = tris;
aMesh.vertices = verts2;
}
/// <summary>
/// Reduces poly count of the mesh while trying to preserve features.
/// </summary>
/// <param name="om"> Mesh to simplify. </param>
/// <param name="ratio"> Percent of the triangles the new mesh will have </param>
/// <returns> </returns>
private static Mesh GenerateSimplifiedMesh(ref Mesh om, ref Mesh dummyMesh, float ratio)
{
MeshDecimate meshDecimate = new();
meshDecimate.ratio = ratio;
meshDecimate.PreCalculate(om);
meshDecimate.Calculate(om);
Mesh sm = new();
sm.vertices = meshDecimate.finalVertices;
sm.triangles = meshDecimate.finalTriangles;
sm.normals = meshDecimate.finalNormals;
sm.name = "DWP_SIM_MESH";
return sm;
}
/// <summary>
/// Calculates the signed volume of a triangle with respect to the origin.
/// Used for mesh volume calculations.
/// </summary>
/// <param name="p1">First vertex.</param>
/// <param name="p2">Second vertex.</param>
/// <param name="p3">Third vertex.</param>
/// <returns>Signed volume of the triangle.</returns>
public static float SignedVolumeOfTriangle(Vector3 p1, Vector3 p2, Vector3 p3)
{
float v321 = p3.x * p2.y * p1.z;
float v231 = p2.x * p3.y * p1.z;
float v312 = p3.x * p1.y * p2.z;
float v132 = p1.x * p3.y * p2.z;
float v213 = p2.x * p1.y * p3.z;
float v123 = p1.x * p2.y * p3.z;
return 1.0f / 6.0f * (-v321 + v231 + v312 - v132 - v213 + v123);
}
/// <summary>
/// Calculates the volume of a mesh in world space.
/// Takes into account the transform's scale, position and rotation.
/// </summary>
/// <param name="mesh">Mesh to calculate volume for.</param>
/// <param name="transform">Transform containing scale information.</param>
/// <returns>Volume of the mesh in cubic meters.</returns>
public static float VolumeOfMesh(Mesh mesh, Transform transform)
{
float volume = 0;
Vector3[] vertices = mesh.vertices;
int[] triangles = mesh.triangles;
Matrix4x4 transformMatrix = transform.localToWorldMatrix;
for (int i = 0; i < mesh.triangles.Length; i += 3)
{
Vector3 p1 = transformMatrix.MultiplyPoint(vertices[triangles[i + 0]]);
Vector3 p2 = transformMatrix.MultiplyPoint(vertices[triangles[i + 1]]);
Vector3 p3 = transformMatrix.MultiplyPoint(vertices[triangles[i + 2]]);
volume += SignedVolumeOfTriangle(p1, p2, p3);
}
return Mathf.Abs(volume);
}
/// <summary>
/// Creates a convex hull from the input mesh.
/// Useful for partially hollow meshes or open hulls.
/// </summary>
/// <param name="mesh">Mesh to convexify.</param>
/// <returns>Convex mesh wrapping the input mesh.</returns>
public static Mesh GenerateConvexMesh(Mesh mesh)
{
IEnumerable<Vector3> stars = mesh.vertices;
Mesh m = new();
List<int> triangles = new();
List<Vertex> vertices = stars.Select(x => new Vertex(x)).ToList();
ConvexHull<Vertex, DefaultConvexFace<Vertex>> result = ConvexHull.Create(vertices);
m.vertices = result.Points.Select(x => x.ToVec()).ToArray();
List<Vertex> xxx = result.Points.ToList();
foreach (DefaultConvexFace<Vertex> face in result.Faces)
{
triangles.Add(xxx.IndexOf(face.Vertices[0]));
triangles.Add(xxx.IndexOf(face.Vertices[1]));
triangles.Add(xxx.IndexOf(face.Vertices[2]));
}
m.triangles = triangles.ToArray();
m.RecalculateNormals();
m.name = "DWP_SIM_MESH";
return m;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9bdaf727cb1f5724aa53bbd6e62bb7a9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,65 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using System;
using UnityEngine;
#endregion
namespace NWH.DWP2.WaterObjects
{
/// <summary>
/// Serializable representation of a mesh.
/// Used to store simulation mesh data without keeping mesh references in game files.
/// </summary>
[Serializable]
public class SerializedMesh
{
/// <summary>
/// Triangle indices of the mesh.
/// </summary>
[SerializeField]
public int[] triangles;
/// <summary>
/// Vertex positions of the mesh.
/// </summary>
[SerializeField]
public Vector3[] vertices;
/// <summary>
/// Serializes a mesh into vertex and triangle arrays.
/// </summary>
/// <param name="mesh">Mesh to serialize.</param>
public void Serialize(Mesh mesh)
{
vertices = mesh.vertices;
triangles = mesh.triangles;
}
/// <summary>
/// Deserializes the stored data back into a Mesh object.
/// </summary>
/// <returns>Reconstructed mesh or null if data is invalid.</returns>
public Mesh Deserialize()
{
if (vertices != null && triangles != null)
{
Mesh m = MeshUtility.GenerateMesh(vertices, triangles);
m.name = "DWP_SIM_MESH";
return m;
}
return null;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dc00acaede32ab3419ea82210d3abb43
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a203ee8c91bbba5449f330d55ce28696
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,72 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using NWH.Common.Utility;
using NWH.DWP2.WaterObjects;
using UnityEngine;
#endregion
namespace NWH.DWP2.WaterData
{
/// <summary>
/// Simple water data provider for flat, static water surfaces.
/// Uses a constant water height based on the transform's Y position.
/// Does not support water height queries, waves, normals, or flow.
/// </summary>
public class FlatWaterDataProvider : WaterDataProvider
{
public override bool SupportsWaterHeightQueries()
{
return false;
}
public override bool SupportsWaterNormalQueries()
{
return false;
}
public override bool SupportsWaterFlowQueries()
{
return false;
}
public override void GetWaterHeights(WaterObject waterObject, ref Vector3[] points, ref float[] waterHeights)
{
float waterHeight = transform.position.y;
waterHeights.Fill(waterHeight);
}
}
}
#if UNITY_EDITOR
namespace NWH.DWP2.WaterData
{
using UnityEditor;
[CustomEditor(typeof(FlatWaterDataProvider))]
[CanEditMultipleObjects]
public class FlatWaterDataProviderEditor : WaterDataProviderEditor
{
protected override void DrawStatus(WaterDataProvider provider)
{
drawer.BeginSubsection("Status");
drawer.Info($"Water height: {provider.transform.position.y:F2}m (from transform Y position)");
drawer.EndSubsection();
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9f46e165d5079ce41ba5716d9518728c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,238 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using NWH.DWP2.WaterObjects;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
#endregion
namespace NWH.DWP2.WaterData
{
/// <summary>
/// Water data provider that uses raycasting to detect water surface.
/// Works with any water system that has a collider.
/// Supports water height and normal queries through raycasting.
/// Uses Unity's job system for parallel raycasts.
/// </summary>
public class RaycastWaterDataProvider : WaterDataProvider
{
/// <summary>
/// Number of raycast commands to process per job batch.
/// Higher values may improve performance on systems with more CPU cores.
/// </summary>
[Tooltip("Minimum number of RaycastCommands per job.")]
public int commandsPerJob = 16;
/// <summary>
/// Layer that floating objects are on.
/// Used to prevent physics collisions between water and objects.
/// </summary>
[Tooltip(
" Layer the water object(s) are on. Required to be able to disable physics collisions between water and object.")]
public int objectLayer = 12;
/// <summary>
/// Maximum distance raycasts can travel in each direction.
/// Raycasts extend this distance above and below each point.
/// Lower values improve performance but may miss water surfaces that are far from the object.
/// </summary>
[Tooltip(
" Raycasts will start at this distance above the point and extend this distance below the point. This means\r\n that if the water surface is raycastDistance below or above the point, it will not be detected.\r\n Using lower value will slightly improve performance of Raycasts.")]
public float raycastDistance = 100f;
/// <summary>
/// Layer that water surface colliders are on.
/// Used to filter raycasts and prevent physics collisions.
/// </summary>
[Tooltip(
" Layer the water is on. Required to be able to disable physics collisions between water and object.")]
public int waterLayer = 4;
protected Vector3 _flow;
protected RaycastHit _hit;
protected LayerMask _layerMask;
protected Mesh _mesh;
protected Vector3[] _normals;
protected int _prevDataSize;
protected QueryParameters _queryParameters;
protected Ray _ray;
protected NativeArray<RaycastCommand> _raycastCommands;
protected NativeArray<RaycastHit> _raycastHits;
protected JobHandle _raycastJobHandle;
protected Vector3 _rayDirection;
protected Vector3 _rayStartOffset;
protected Vector3 _tmp;
protected RaycastCommand _tmpCommand;
protected Vector3 _upVector;
protected Vector2 _uv4;
protected Vector3 _vertDir;
protected int _vertIndex;
protected Vector3 _zeroVector;
public override void Awake()
{
base.Awake();
Physics.IgnoreLayerCollision(waterLayer, objectLayer);
_rayDirection = -Vector3.up;
_rayStartOffset = -_rayDirection * raycastDistance * 0.5f;
_prevDataSize = -1;
_zeroVector = Vector3.zero;
_upVector = Vector3.up;
_queryParameters = new QueryParameters();
}
public override bool SupportsWaterHeightQueries()
{
return true;
}
public override bool SupportsWaterNormalQueries()
{
return true;
}
public override bool SupportsWaterFlowQueries()
{
return false;
}
public override void GetWaterHeights(WaterObject waterObject, ref Vector3[] points, ref float[] waterHeights)
{
int n = points.Length;
bool queriesHitBackfaces = Physics.queriesHitBackfaces;
bool queriesHitTriggers = Physics.queriesHitTriggers;
try
{
Physics.queriesHitBackfaces = false;
Physics.queriesHitTriggers = false;
if (n != _prevDataSize)
{
_normals = new Vector3[n];
Deallocate();
_raycastCommands = new NativeArray<RaycastCommand>(n, Allocator.Persistent);
_raycastHits = new NativeArray<RaycastHit>(n, Allocator.Persistent);
}
_queryParameters.layerMask = 1 << waterLayer;
for (int i = 0; i < n; i++)
{
_tmpCommand.from = points[i] + _rayStartOffset;
_tmpCommand.direction = _rayDirection;
_tmpCommand.distance = raycastDistance;
_tmpCommand.queryParameters = _queryParameters;
_raycastCommands[i] = _tmpCommand;
}
// Schedule raycast batch (removed duplicate scheduling that wasted 50% CPU time)
_raycastJobHandle = RaycastCommand.ScheduleBatch(_raycastCommands, _raycastHits, 16);
_raycastJobHandle.Complete();
Vector3 hitNormal;
for (int i = 0; i < n; i++)
{
hitNormal = _raycastHits[i].normal;
waterHeights[i] = _raycastHits[i].point.y;
_normals[i] = hitNormal == _zeroVector ? _upVector : hitNormal;
}
}
finally
{
// Always restore global physics settings, even if an exception occurs
Physics.queriesHitBackfaces = queriesHitBackfaces;
Physics.queriesHitTriggers = queriesHitTriggers;
}
_prevDataSize = n;
}
public override void GetWaterNormals(WaterObject waterObject, ref Vector3[] points, ref Vector3[] waterNormals)
{
waterNormals = _normals;
}
public virtual void OnDisable()
{
Deallocate();
}
public virtual void OnDestroy()
{
Deallocate();
}
/// <summary>
/// Deallocates native arrays used for raycasting.
/// Called automatically on disable or destroy.
/// </summary>
public virtual void Deallocate()
{
_raycastJobHandle.Complete();
if (_raycastCommands.IsCreated)
{
_raycastCommands.Dispose();
}
if (_raycastHits.IsCreated)
{
_raycastHits.Dispose();
}
}
}
}
#if UNITY_EDITOR
namespace NWH.DWP2.WaterData
{
using UnityEditor;
[CustomEditor(typeof(RaycastWaterDataProvider))]
[CanEditMultipleObjects]
public class RaycastWaterDataProviderEditor : WaterDataProviderEditor
{
protected override void DrawStatus(WaterDataProvider provider)
{
drawer.BeginSubsection("Status");
drawer.Info("Uses physics raycasts to detect water surface colliders.");
drawer.EndSubsection();
}
protected override void DrawSettings(WaterDataProvider provider)
{
drawer.BeginSubsection("Settings");
drawer.Field("waterLayer");
drawer.Field("objectLayer");
drawer.Field("raycastDistance", true, "m");
drawer.Field("commandsPerJob");
drawer.EndSubsection();
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 223c7c03b51c437d903c9d67b650402e
timeCreated: 1594572266

View File

@@ -0,0 +1,316 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using NWH.DWP2.WaterObjects;
using UnityEngine;
#endregion
namespace NWH.DWP2.WaterData
{
/// <summary>
/// Base class for providing water surface data to WaterObjects.
/// Implementations provide water height, flow velocity, and surface normal information.
/// Uses a trigger collider to automatically detect and register WaterObjects that enter the water.
/// Inherit from this class to create adapters for different water systems.
/// </summary>
public abstract class WaterDataProvider : MonoBehaviour
{
protected float[] _singleHeightArray;
protected Vector3[] _singlePointArray;
protected Collider _triggerCollider;
public virtual void Awake()
{
_singleHeightArray = new float[1];
_singlePointArray = new Vector3[1];
_triggerCollider = GetComponent<Collider>();
if (_triggerCollider == null)
{
// Debug.LogWarning("WaterDataProvider requires a Collider with 'Is Trigger' ticked to be present " +
// "on the same GameObject to act as a trigger volume. Creating one.");
_triggerCollider = gameObject.AddComponent<SphereCollider>();
_triggerCollider.isTrigger = true;
((SphereCollider)_triggerCollider).radius = 1000000f;
}
if (!_triggerCollider.isTrigger)
{
Debug.LogWarning("WaterDataProvider Collider has to have 'Is Trigger' ticked. Fixing.");
_triggerCollider.isTrigger = true;
}
}
private void OnDrawGizmos()
{
Gizmos.color = Color.green;
Gizmos.DrawSphere(transform.position, 0.1f);
}
private void OnTriggerEnter(Collider other)
{
// Assign the current water data provider
Rigidbody targetRigidbody = other.attachedRigidbody;
if (targetRigidbody != null)
{
WaterObject[] targetWaterObjects = targetRigidbody.GetComponentsInChildren<WaterObject>();
for (int i = 0; i < targetWaterObjects.Length; i++)
{
targetWaterObjects[i].OnEnterWaterDataProvider(this);
}
}
}
private void OnTriggerExit(Collider other)
{
// Assign the current water data provider
Rigidbody targetRigidbody = other.attachedRigidbody;
if (targetRigidbody != null)
{
WaterObject[] targetWaterObjects = targetRigidbody.GetComponentsInChildren<WaterObject>();
for (int i = 0; i < targetWaterObjects.Length; i++)
{
targetWaterObjects[i].OnExitWaterDataProvider(this);
}
}
}
/// <summary>
/// Does this water system support water height queries?
/// </summary>
/// <returns> True if it does, false if it does not. </returns>
public abstract bool SupportsWaterHeightQueries();
/// <summary>
/// Does this water system support water normal queries?
/// </summary>
/// <returns> True if it does, false if it does not. </returns>
public abstract bool SupportsWaterNormalQueries();
/// <summary>
/// Does this water system support water velocity queries?
/// </summary>
/// <returns> True if it does, false if it does not. </returns>
public abstract bool SupportsWaterFlowQueries();
/// <summary>
/// Returns water height at each given point.
/// Override this method to provide water height data from your water system.
/// </summary>
/// <param name="waterObject">WaterObject requesting the data.</param>
/// <param name="points">Position array in world coordinates.</param>
/// <param name="waterHeights">Water height array in world coordinates. Corresponds to positions.</param>
public virtual void GetWaterHeights(WaterObject waterObject, ref Vector3[] points, ref float[] waterHeights)
{
// Do nothing. This will use the initial values of water heights (0).
}
/// <summary>
/// Returns water flow velocity at each given point.
/// Override this method to provide water flow data from your water system.
/// Flow should be in world space and relative to the world, not the WaterObject.
/// </summary>
/// <param name="waterObject">WaterObject requesting the data.</param>
/// <param name="points">Position array in world coordinates.</param>
/// <param name="waterFlows">Water flow velocity array in world coordinates. Corresponds to positions.</param>
public virtual void GetWaterFlows(WaterObject waterObject, ref Vector3[] points, ref Vector3[] waterFlows)
{
// Do nothing. This will use the initial values of water velocities (0,0,0).
}
/// <summary>
/// Returns water surface normals at each given point.
/// Override this method to provide water normal data from your water system.
/// </summary>
/// <param name="waterObject">WaterObject requesting the data.</param>
/// <param name="points">Position array in world coordinates.</param>
/// <param name="waterNormals">Water surface normal array in world coordinates. Corresponds to positions.</param>
public virtual void GetWaterNormals(WaterObject waterObject, ref Vector3[] points, ref Vector3[] waterNormals)
{
// Do nothing. This will use the initial values of water normals (0,0,0).
}
/// <summary>
/// Queries water data based on enabled flags.
/// Calls the appropriate getter methods based on which data types are requested.
/// </summary>
/// <param name="waterObject">WaterObject requesting the data.</param>
/// <param name="points">Position array in world coordinates.</param>
/// <param name="waterHeights">Output water heights array.</param>
/// <param name="waterFlows">Output water flows array.</param>
/// <param name="waterNormals">Output water normals array.</param>
/// <param name="useWaterHeight">Should water heights be queried.</param>
/// <param name="useWaterNormals">Should water normals be queried.</param>
/// <param name="useWaterFlow">Should water flows be queried.</param>
public void GetWaterHeightsFlowsNormals(WaterObject waterObject, ref Vector3[] points, ref float[] waterHeights,
ref Vector3[] waterFlows, ref Vector3[] waterNormals, bool useWaterHeight, bool useWaterNormals,
bool useWaterFlow)
{
if (useWaterHeight)
{
GetWaterHeights(waterObject, ref points, ref waterHeights);
}
if (useWaterFlow && SupportsWaterFlowQueries())
{
GetWaterFlows(waterObject, ref points, ref waterFlows);
}
if (useWaterNormals && SupportsWaterNormalQueries())
{
GetWaterNormals(waterObject, ref points, ref waterNormals);
}
}
/// <summary>
/// Returns water height at a single point.
/// Less efficient than batch queries but useful for one-off checks.
/// </summary>
/// <param name="waterObject">WaterObject requesting the data.</param>
/// <param name="point">Position in world coordinates.</param>
/// <returns>Water height at the given point.</returns>
public virtual float GetWaterHeightSingle(WaterObject waterObject, Vector3 point)
{
_singlePointArray[0] = point;
GetWaterHeights(waterObject, ref _singlePointArray, ref _singleHeightArray);
return _singleHeightArray[0];
}
/// <summary>
/// Checks if a point is underwater.
/// Accounts for wave height when water normals are supported.
/// </summary>
/// <param name="waterObject">WaterObject making the query.</param>
/// <param name="worldPoint">Position to check in world coordinates.</param>
/// <returns>True if the point is below the water surface.</returns>
public bool PointInWater(WaterObject waterObject, Vector3 worldPoint)
{
return GetWaterHeight(waterObject, worldPoint) > worldPoint.y;
}
/// <summary>
/// Returns water height at the given world position.
/// </summary>
/// <param name="waterObject">WaterObject making the query.</param>
/// <param name="worldPoint">Position in world coordinates.</param>
/// <returns>Water surface height at the given point.</returns>
public float GetWaterHeight(WaterObject waterObject, Vector3 worldPoint)
{
return GetWaterHeightSingle(waterObject, worldPoint);
}
}
}
#if UNITY_EDITOR
namespace NWH.DWP2.WaterData
{
using NWH.NUI;
using UnityEditor;
/// <summary>
/// Base editor for WaterDataProvider implementations.
/// Draws common UI elements (capabilities, trigger volume) and provides virtual methods for derived editors.
/// </summary>
public class WaterDataProviderEditor : DWP2NUIEditor
{
public override bool OnInspectorNUI()
{
if (!base.OnInspectorNUI()) return false;
var provider = (WaterDataProvider)target;
DrawStatus(provider);
DrawTriggerVolume(provider);
DrawCapabilities(provider);
DrawSettings(provider);
drawer.EndEditor(this);
return true;
}
/// <summary>
/// Override to draw provider-specific status information.
/// </summary>
protected virtual void DrawStatus(WaterDataProvider provider)
{
}
/// <summary>
/// Draws the trigger volume information.
/// </summary>
protected virtual void DrawTriggerVolume(WaterDataProvider provider)
{
drawer.BeginSubsection("Trigger Volume");
var collider = provider.GetComponent<UnityEngine.Collider>();
if (collider != null)
{
string colliderType = collider.GetType().Name;
string size = collider switch
{
UnityEngine.BoxCollider box => $"{box.size.x:F1} x {box.size.y:F1} x {box.size.z:F1}",
UnityEngine.SphereCollider sphere => $"Radius: {sphere.radius:F1}",
UnityEngine.CapsuleCollider capsule => $"Radius: {capsule.radius:F1}, Height: {capsule.height:F1}",
UnityEngine.MeshCollider mesh => mesh.sharedMesh != null ? mesh.sharedMesh.name : "No mesh",
_ => ""
};
drawer.Info($"{colliderType}{(size.Length > 0 ? $" ({size})" : "")}");
if (!collider.isTrigger)
{
drawer.Info("Collider is not set as trigger!", MessageType.Warning);
}
}
else
{
drawer.Info("No collider found. A large sphere trigger will be auto-created at runtime.", MessageType.Warning);
}
drawer.EndSubsection();
}
/// <summary>
/// Draws the capabilities section showing supported query types.
/// </summary>
protected virtual void DrawCapabilities(WaterDataProvider provider)
{
drawer.BeginSubsection("Capabilities");
drawer.Label($"Water Height: {(provider.SupportsWaterHeightQueries() ? "Yes" : "No")}");
drawer.Label($"Water Normal: {(provider.SupportsWaterNormalQueries() ? "Yes" : "No")}");
drawer.Label($"Water Flow: {(provider.SupportsWaterFlowQueries() ? "Yes" : "No")}");
drawer.EndSubsection();
}
/// <summary>
/// Override to draw provider-specific settings.
/// </summary>
protected virtual void DrawSettings(WaterDataProvider provider)
{
}
public override bool UseDefaultMargins() => false;
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c4206096a6ed32444ab698262cf0dfa5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 211ca72301d713c4b8a753757f4d03f6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dcbce59eb9e305848972c15acdf24261
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,88 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#if UNITY_EDITOR
#region
using NWH.NUI;
using NWH.DWP2.WaterObjects;
using UnityEditor;
using UnityEngine;
#endregion
namespace NWH.DWP2.WaterEffects
{
/// <summary>
/// Custom inspector for WaterParticleSystem.
/// </summary>
[CustomEditor(typeof(WaterParticleSystem))]
[CanEditMultipleObjects]
public class WaterParticleSystemEditor : DWP2NUIEditor
{
private WaterParticleSystem wps;
/// <summary>
/// Draws custom inspector GUI for WaterParticleSystem.
/// </summary>
public override bool OnInspectorNUI()
{
if (!base.OnInspectorNUI())
{
return false;
}
wps = (WaterParticleSystem)target;
// Draw logo texture
Rect logoRect = drawer.positionRect;
logoRect.height = 60f;
drawer.DrawEditorTexture(logoRect, "Dynamic Water Physics 2/Logos/WaterParticleSystemLogo");
drawer.AdvancePosition(logoRect.height);
drawer.BeginSubsection("Particle Settings");
drawer.Field("emit");
drawer.Field("renderQueue");
drawer.Field("startSize");
drawer.Field("sleepThresholdVelocity");
drawer.Field("initialVelocityModifier");
drawer.Field("maxInitialAlpha");
drawer.Field("initialAlphaModifier");
drawer.Field("emitPerCycle");
drawer.Field("emitTimeInterval");
drawer.Field("positionExtrapolationFrames");
drawer.Field("surfaceElevation");
drawer.EndSubsection();
/* // TODO - move this from editor script
if(!wps.GetComponent<ParticleSystem>())
{
GameObject waterParticleSystemPrefab = Resources.Load<GameObject>("Dynamic Water Physics
2/WaterParticleSystemPrefab");
if (waterParticleSystemPrefab == null)
{
Debug.LogError("Could not load WaterParticleSystemPrefab from Resources.");
}
else
{
UnityEditorInternal.ComponentUtility.CopyComponent(waterParticleSystemPrefab
.GetComponent<ParticleSystem>());
UnityEditorInternal.ComponentUtility.PasteComponentAsNew(wps.gameObject);
}
}
*/
drawer.EndEditor(this);
return true;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 53231489df986aa4cb71dddc0b72afaf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,65 @@
Shader "WaterFX/WaterParticle" {
Properties{
_TintColor("Tint Color", Color) = (0.5,0.5,0.5,0.5)
_MainTex("Particle Texture", 2D) = "white" {}
}
Category
{
Tags{ "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "PreviewType" = "Plane" }
Blend SrcAlpha OneMinusSrcAlpha
ColorMask RGB
Cull Off Lighting Off ZWrite Off
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
fixed4 _TintColor;
struct appdata_t {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f {
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_FOG_COORDS(1)
UNITY_VERTEX_OUTPUT_STEREO
};
float4 _MainTex_ST;
v2f vert(appdata_t v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
o.vertex = UnityObjectToClipPos(v.vertex);
o.color = v.color * _TintColor;
o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = 2 * i.color * tex2D(_MainTex, i.texcoord);
return col;
}
ENDCG
}
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 44e1bf9aba10f314fbc1aacd1e602f07
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,320 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using NWH.Common.Utility;
using UnityEngine;
using UnityEngine.Serialization;
using Random = UnityEngine.Random;
#endregion
namespace NWH.DWP2.WaterObjects
{
/// <summary>
/// Generates water spray and splash particles based on WaterObject simulation data.
/// Emits particles at the waterline where triangles intersect the water surface.
/// Particle emission is controlled by object velocity and triangle forces.
/// Requires a ParticleSystem component.
/// </summary>
[RequireComponent(typeof(ParticleSystem))]
public class WaterParticleSystem : MonoBehaviour
{
/// <summary>
/// Should the particle system emit?
/// </summary>
[Tooltip("Should the particle system emit?")]
public bool emit = true;
/// <summary>
/// How many particles should be emitted each 'emitTimeInterval' seconds.
/// </summary>
[Tooltip("How many particles should be emitted each 'emitTimeInterval' seconds.")]
[Range(0f, 20f)]
public int emitPerCycle = 6;
/// <summary>
/// Determines how often the particles will be emitted.
/// </summary>
[Tooltip("Determines how often the particles will be emitted.")]
[Range(0f, 0.1f)]
public float emitTimeInterval = 0.04f;
/// <summary>
/// Multiplies initial alpha by this value. Alpha cannot be higher than maxInitialAlpha.
/// </summary>
[Tooltip("Multiplies initial alpha by this value. Alpha cannot be higher than maxInitialAlpha.")]
[Range(0f, 10f)]
public float initialAlphaModifier = 0.4f;
/// <summary>
/// Determines how much velocity of the object will affect initial particle speed.
/// </summary>
[Tooltip("Determines how much velocity of the object will affect initial particle speed.")]
[Range(0f, 5f)]
public float initialVelocityModifier = 0.01f;
/// <summary>
/// Limit initial alpha to this value.
/// </summary>
[Tooltip("Limit initial alpha to this value.")]
[Range(0f, 1f)]
public float maxInitialAlpha = 0.15f;
/// <summary>
/// Number of frames ahead to predict particle emission position.
/// Higher values spawn particles ahead of the object for better visual continuity.
/// </summary>
[Tooltip("Script will try to predict where the object will be in the next n frames.")]
public int positionExtrapolationFrames = 4;
/// <summary>
/// WaterObject to read simulation data from.
/// Will auto-detect from parent if not assigned.
/// </summary>
public WaterObject ReferenceWaterObject;
/// <summary>
/// Render queue of the particle material.
/// </summary>
[Tooltip("Render queue of the particle material.")]
public int renderQueue = 2700;
/// <summary>
/// Velocity object has to have to emit particles.
/// </summary>
[FormerlySerializedAs("sleepTresholdVelocity")]
[Tooltip("Velocity object has to have to emit particles.")]
[Range(0.1f, 5f)]
public float sleepThresholdVelocity = 1.5f;
/// <summary>
/// Initial size of the particle.
/// </summary>
[Tooltip("Initial size of the particle.")]
[Range(0f, 64f)]
public float startSize = 4f;
/// <summary>
/// Elevation above water at which the particles will spawn. Used to avoid clipping.
/// </summary>
[Tooltip("Elevation above water at which the particles will spawn. Used to avoid clipping.")]
[Range(0f, 0.1f)]
public float surfaceElevation = 0.016f;
private ParticleSystem.NoiseModule _noiseModule;
private ParticleSystem _particleSystem;
private int _prevTriCount;
private WaterObject _targetWaterObject;
private float _timeElapsed;
private int _waterlineCount;
private int[] _waterlineIndices;
private void Start()
{
if (ReferenceWaterObject == null)
{
ReferenceWaterObject = GetComponentInParent<WaterObject>();
}
_targetWaterObject = transform.GetComponentInParentsOrChildren<WaterObject>();
if (_targetWaterObject == null)
{
Debug.LogError(
$"{name}: WaterParticleSystem requires WaterObject attached to the same object or one of parent objects to function.");
return;
}
_particleSystem = GetComponent<ParticleSystem>();
if (_particleSystem == null)
{
Debug.LogError("No ParticleSystem found.");
}
_particleSystem.GetComponent<Renderer>().material.renderQueue = renderQueue;
_noiseModule = _particleSystem.noise;
_prevTriCount = -999;
}
private void LateUpdate()
{
if (!emit)
{
return;
}
int triCount = _targetWaterObject.triangleCount;
if (triCount > 0 && _prevTriCount != triCount)
{
_waterlineIndices = new int[triCount];
}
if (_targetWaterObject.targetRigidbody.linearVelocity.magnitude > sleepThresholdVelocity)
{
EmitNew();
}
_timeElapsed += Time.deltaTime;
_prevTriCount = triCount;
}
private void OnDrawGizmosSelected()
{
if (_waterlineIndices == null)
{
return;
}
Gizmos.color = Color.magenta;
for (int i = 0; i < _waterlineIndices.Length; i++)
{
if (_targetWaterObject.ResultStates[_waterlineIndices[i]] != 1)
{
continue;
}
Vector3 a = _targetWaterObject.ResultP0s[_waterlineIndices[i] * 6 + 2];
Vector3 b = _targetWaterObject.ResultP0s[_waterlineIndices[i] * 6 + 1];
Gizmos.DrawLine(a, b);
}
}
private void OnDestroy()
{
if (!Application.isPlaying)
{
DestroyImmediate(_particleSystem);
}
}
private void EmitNew()
{
if (_targetWaterObject == null)
{
return;
}
int triCount = _targetWaterObject.triangleCount;
if (emit && _timeElapsed >= emitTimeInterval && triCount > 0f)
{
_timeElapsed = 0;
int emitted = 0;
// Emit allowed number of particles
float elevation = 0;
if (ReferenceWaterObject != null)
{
elevation = ReferenceWaterObject.GetWaterHeightSingle(Vector3.zero);
}
else
{
Debug.LogWarning("Will not emit. WaterDataProvider is not present in the scene.");
}
_waterlineCount = 0;
for (int i = 0; i < triCount; i++)
{
if (_targetWaterObject.ResultStates[i] != 1)
{
continue;
}
_waterlineIndices[_waterlineCount] = i;
_waterlineCount++;
}
if (_waterlineCount == 0)
{
return;
}
float noise = startSize > 1f ? Mathf.Sqrt(startSize) * 0.1f : startSize * 0.1f;
_noiseModule.strengthX = noise;
_noiseModule.strengthY = 0f;
_noiseModule.strengthZ = noise;
while (emitted < emitPerCycle)
{
int i = Random.Range(0, _waterlineCount);
int waterLineTriIndex = _waterlineIndices[i];
EmitParticle(
_targetWaterObject.ResultP0s[waterLineTriIndex * 6 + 2],
_targetWaterObject.ResultP0s[waterLineTriIndex * 6 + 1],
elevation,
_targetWaterObject.ResultVelocities[waterLineTriIndex],
_targetWaterObject.ResultNormals[waterLineTriIndex],
_targetWaterObject.ResultForces[waterLineTriIndex],
_targetWaterObject.ResultAreas[waterLineTriIndex]);
emitted++;
}
}
}
/// <summary>
/// Emits a single particle at the waterline between two points.
/// Particle properties are calculated from triangle simulation data.
/// </summary>
/// <param name="p0">First waterline point.</param>
/// <param name="p1">Second waterline point.</param>
/// <param name="elevation">Water surface elevation.</param>
/// <param name="velocity">Triangle velocity.</param>
/// <param name="normal">Triangle normal.</param>
/// <param name="force">Force acting on triangle.</param>
/// <param name="area">Triangle area.</param>
private void EmitParticle(Vector3 p0, Vector3 p1, float elevation, Vector3 velocity, Vector3 normal,
Vector3 force, float area)
{
if (area < 0.0001f)
{
return;
}
// Start velocity
Vector3 startVelocity = normal * velocity.magnitude;
startVelocity.y = 0f;
startVelocity *= initialVelocityModifier;
// Start position
Vector3 emissionPoint = (p0 + p1) / 2f;
emissionPoint += Time.deltaTime * positionExtrapolationFrames * velocity;
emissionPoint.y = elevation + surfaceElevation;
float normalizedForce = force.magnitude / area;
float startAlpha = Mathf.Clamp(normalizedForce * 0.00005f * initialAlphaModifier, 0f, maxInitialAlpha);
Color startColor = new(1f, 1f, 1f, startAlpha);
float size = startSize;
if (startAlpha < 0.001f)
{
return;
}
ParticleSystem.EmitParams emitParams = new()
{
startColor = startColor,
position = emissionPoint,
velocity = startVelocity,
startSize = size,
};
_particleSystem.Emit(emitParams, 1);
_particleSystem.Play();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fd49d8bec9d80fd4ba7c4b663a177c6e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9118365f988afd4419c64e108a8bbc64
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 100
icon: {fileID: 2800000, guid: ff137dafe58a6324c908e3cb796b45b9, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,40 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using NWH.DWP2.WaterObjects;
using UnityEngine;
#endregion
namespace NWH.DWP2.DemoContent
{
/// <summary>
/// An example on how to add WaterObject to an existing object at runtime.
/// </summary>
public class WaterObjectFromScript : MonoBehaviour
{
private void Start()
{
WaterObject waterObject = gameObject.AddComponent<WaterObject>();
waterObject.convexifyMesh = true;
waterObject.simplifyMesh = true;
waterObject.targetTriangleCount = 64;
waterObject.GenerateSimMesh();
MassFromVolume massFromVolume = gameObject.AddComponent<MassFromVolume>();
massFromVolume.SetDefaultAsMaterial(); // Use massFromVolume.SetMaterial() to use a different material
// instead of the default one.
// Important. Without running Synchronize() WaterObject will not be registered by the WaterObjectManager and
// the physics will not work. Just note that running Synchronize() can be expensive, so call it only after
// all WaterObject setup is complete.
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6635e16fc8df26d42ac0ca2af54e0d0e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3d9c6f1ca4a146478a4b043413dbbebe
timeCreated: 1593008450

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6155482a401f431aa1d81663d1ef6328
timeCreated: 1593008532

View File

@@ -0,0 +1,46 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#if UNITY_EDITOR
#region
using NWH.NUI;
using NWH.DWP2.WaterObjects;
using UnityEditor;
#endregion
namespace NWH.DWP2
{
/// <summary>
/// Custom inspector for WaterObjectMaterial.
/// </summary>
[CustomEditor(typeof(WaterObjectMaterial))]
[CanEditMultipleObjects]
public class WaterObjectMaterialEditor : DWP2NUIEditor
{
/// <summary>
/// Draws custom inspector GUI for WaterObjectMaterial.
/// </summary>
public override bool OnInspectorNUI()
{
if (!base.OnInspectorNUI())
{
return false;
}
drawer.Field("density");
drawer.EndEditor(this);
return true;
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7eea0dd9f8424a68a8be74e7efd1b4fa
timeCreated: 1593008530

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 42d77d1ef45b8a3409309e441a100e2b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Aluminum
m_EditorClassIdentifier:
density: 2700

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e662794a6e68dba4cadc5c4d1e2ae943
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Bottle - Empty
m_EditorClassIdentifier:
density: 80

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: be16c1016c27c3542ac565a3f3c35650
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Brick
m_EditorClassIdentifier:
density: 1800

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a9bf2850b5467f94385f721462f69115
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Concrete
m_EditorClassIdentifier:
density: 2300

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b4c3c2ad764665344be08dd3dd062838
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Copper
m_EditorClassIdentifier:
density: 8300

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 89fbf5fa6b420cc4085be0019b14d787
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Cork
m_EditorClassIdentifier:
density: 120

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7a1c17fc0b7d09546b49c0193ea37d7f
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Default
m_EditorClassIdentifier:
density: 500

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a43f47272e61c6544964556b82effb8f
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Glass
m_EditorClassIdentifier:
density: 2700

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f119dee50e10486419f9a6b0133099dc
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: HumanBody
m_EditorClassIdentifier:
density: 950

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: eca70d469947b9844b97db7f30aac137
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Ice
m_EditorClassIdentifier:
density: 920

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 306f40901719f4040955d6e5dd9d963f
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Lead
m_EditorClassIdentifier:
density: 11300

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 247bba41d6378e44db048875c6da8551
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Oil
m_EditorClassIdentifier:
density: 920

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 33968881da0b22e44bc7d19bf2db5b7e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Plastic
m_EditorClassIdentifier:
density: 1200

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 093f7874d7a6d6445abf82fd6c58ae02
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Rubber
m_EditorClassIdentifier:
density: 1200

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1e0a6964be0e52847b9601e732c2b54a
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Sand
m_EditorClassIdentifier:
density: 1500

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 01475980b546ea94fa509e12808073c2
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Snow
m_EditorClassIdentifier:
density: 560

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ac07bed869159c24ba7a8b21bf87ed01
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Steel
m_EditorClassIdentifier:
density: 7800

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d0d15a947a6ba224fb501c3b65fa59ba
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Titanium
m_EditorClassIdentifier:
density: 4500

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 317fa8ed3a25ba745b8cafd75e9dccb9
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Wood - Aspen
m_EditorClassIdentifier:
density: 420

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b86190ebd65a8704cbea83fd71b016df
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Wood - Balsa
m_EditorClassIdentifier:
density: 160

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 806fe45366f6c0d408aa620e96395d53
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Wood - Beech
m_EditorClassIdentifier:
density: 800

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e2aa2f3f85f035545a44a19bf82c0db5
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Wood - Birch
m_EditorClassIdentifier:
density: 670

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ee8809750e051d944a439de7b897dbfe
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8588a0103d7149e58d7a81d9673d5330, type: 3}
m_Name: Wooden Crate - Empty
m_EditorClassIdentifier:
density: 90

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 97b293688a6d65641853dea8bdf789a8
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using UnityEngine;
#endregion
namespace NWH.DWP2.WaterObjects
{
/// <summary>
/// ScriptableObject defining material properties for mass calculation.
/// Contains density value used by MassFromVolume to calculate object mass.
/// Create via Assets > Create > Dynamic Water Physics 2 > Water Object Material.
/// </summary>
[CreateAssetMenu(fileName = "WaterObjectMaterial", menuName = "Dynamic Water Physics 2/Water Object Material",
order = 0)]
public class WaterObjectMaterial : ScriptableObject
{
/// <summary>
/// Material density in kg/m³.
/// Used with mesh volume to calculate realistic object mass.
/// Examples: Wood ~600, Ice ~920, Aluminum ~2700, Steel ~7850.
/// </summary>
public float density = 600;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8588a0103d7149e58d7a81d9673d5330
timeCreated: 1593008450

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 10a3aeb7928d1c24cac05e4ee0d33433
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 754900265e733354f805bd57279cdfb7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,167 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#if UNITY_EDITOR
#region
using NWH.Common.CoM;
using NWH.NUI;
using NWH.DWP2.WaterObjects;
using UnityEditor;
using UnityEngine;
#endregion
namespace NWH.DWP2
{
/// <summary>
/// Custom inspector for WaterObjectWizard that automates WaterObject setup.
/// </summary>
[CustomEditor(typeof(WaterObjectWizard))]
[CanEditMultipleObjects]
public class WaterObjectWizardEditor : DWP2NUIEditor
{
private bool _wizardFinished;
private WaterObjectWizard wow;
/// <summary>
/// Draws custom inspector GUI for WaterObjectWizard.
/// </summary>
public override bool OnInspectorNUI()
{
if (!base.OnInspectorNUI())
{
return false;
}
serializedObject.Update();
wow = (WaterObjectWizard)target;
Rect logoRect = drawer.positionRect;
logoRect.height = 60f;
drawer.DrawEditorTexture(logoRect, "Dynamic Water Physics 2/Logos/WaterObjectWizardLogo");
drawer.AdvancePosition(logoRect.height);
drawer.BeginSubsection("Options");
drawer.Field("addWaterParticleSystem");
drawer.EndSubsection();
if (drawer.Button("Auto-Setup"))
{
foreach (WaterObjectWizard wow in targets)
{
RunWizard(wow);
}
}
MeshFilter mf = wow.GetComponent<MeshFilter>();
if (mf != null)
{
if (mf.sharedMesh != null)
{
if (mf.sharedMesh.triangles.Length / 3 > 4000)
{
drawer.Info(
"Large mesh detected. Expect WaterObjectWizard to take a few moments to setup this object.");
}
}
}
drawer.EndEditor();
if (_wizardFinished)
{
DestroyImmediate(wow);
}
return true;
}
/// <summary>
/// Executes wizard setup for WaterObject component and dependencies.
/// </summary>
private void RunWizard(WaterObjectWizard wow)
{
GameObject target = wow.gameObject;
// Check for existing water object
if (target.GetComponent<WaterObject>() != null)
{
Debug.LogWarning($"WaterObjectWizard: {target.name} already contains WaterObject component.");
}
// Check for mesh filter
MeshFilter mf = target.GetComponent<MeshFilter>();
if (mf == null)
{
Debug.LogError("WaterObjectWizard: MeshFilter not found. WaterObject requires MeshFilter to work.");
return;
}
// Add rigidbody
Rigidbody parentRigidbody = target.transform.GetComponentInParent<Rigidbody>(true);
if (parentRigidbody == null)
{
Debug.Log("WaterObjectWizard: Parent rigidbody not found. Adding new.");
parentRigidbody = target.AddComponent<Rigidbody>();
parentRigidbody.angularDamping = 0.15f;
parentRigidbody.linearDamping = 0.05f;
parentRigidbody.interpolation = RigidbodyInterpolation.None;
VariableCenterOfMass com = target.AddComponent<VariableCenterOfMass>();
}
// Add collider
int colliderCount = parentRigidbody.transform.GetComponentsInChildren<Collider>().Length;
if (colliderCount == 0)
{
Debug.Log(
$"WaterObjectWizard: Found 0 colliders on object {parentRigidbody.name}. Adding new mesh collider.");
MeshCollider mc = target.AddComponent<MeshCollider>();
mc.convex = true;
mc.isTrigger = false;
}
// Add water object
if (target.GetComponent<WaterObject>() == null)
{
WaterObject wo = target.AddComponent<WaterObject>();
wo.convexifyMesh = true;
wo.simplifyMesh = true;
wo.targetTriangleCount = 64;
wo.GenerateSimMesh();
MassFromVolume massFromVolume = target.AddComponent<MassFromVolume>();
massFromVolume.SetDefaultAsMaterial();
massFromVolume.CalculateAndApplyFromMaterial();
}
// Add Water Particle System and Particle System
if (wow.addWaterParticleSystem)
{
GameObject waterParticleSystemPrefab =
Resources.Load<GameObject>("Dynamic Water Physics 2/DefaultWaterParticleSystem");
if (waterParticleSystemPrefab == null)
{
Debug.LogError("Could not load WaterParticleSystemPrefab from Resources.");
}
else
{
Instantiate(waterParticleSystemPrefab, target.transform);
}
}
_wizardFinished = true;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 64ab77e709b22bb4586c4204dab18bf4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using UnityEngine;
#endregion
namespace NWH.DWP2.WaterObjects
{
/// <summary>
/// Configuration for the WaterObject setup wizard.
/// Used by the editor to automate WaterObject component setup.
/// </summary>
public class WaterObjectWizard : MonoBehaviour
{
/// <summary>
/// Should the wizard add a WaterParticleSystem component for splash effects.
/// </summary>
public bool addWaterParticleSystem;
}
}

Some files were not shown because too many files have changed in this diff Show More