using UnityEngine; using CW.Common; namespace PaintIn3D { /// This allows you to paint a sphere at a hit point. Hit points will automatically be sent by any P3dHit___ component on this GameObject, or its ancestors. [HelpURL(P3dCommon.HelpUrlPrefix + "P3dPaintSphere")] [AddComponentMenu(P3dCommon.ComponentHitMenuPrefix + "Paint Sphere")] public class P3dPaintSphere : MonoBehaviour, IHitPoint, IHitLine, IHitTriangle, IHitQuad, IHitCoord { /// Only the P3dModel/P3dPaintable GameObjects whose layers are within this mask will be eligible for painting. public LayerMask Layers { set { layers = value; } get { return layers; } } [SerializeField] private LayerMask layers = -1; /// Only the P3dPaintableTexture components with a matching group will be painted by this component. public P3dGroup Group { set { group = value; } get { return group; } } [SerializeField] private P3dGroup group; /// If this is set, then only the specified P3dModel/P3dPaintable will be painted, regardless of the layer setting. public P3dModel TargetModel { set { targetModel = value; } get { return targetModel; } } [SerializeField] private P3dModel targetModel; /// If this is set, then only the specified P3dPaintableTexture will be painted, regardless of the layer or group setting. public P3dPaintableTexture TargetTexture { set { targetTexture = value; } get { return targetTexture; } } [SerializeField] private P3dPaintableTexture targetTexture; /// This allows you to choose how the paint from this component will combine with the existing pixels of the textures you paint. /// NOTE: See the Blend Mode section of the documentation for more information. public P3dBlendMode BlendMode { set { blendMode = value; } get { return blendMode; } } [SerializeField] private P3dBlendMode blendMode = P3dBlendMode.AlphaBlend(Vector4.one); /// The color of the paint. public Color Color { set { color = value; } get { return color; } } [SerializeField] private Color color = Color.white; /// The opacity of the brush. public float Opacity { set { opacity = value; } get { return opacity; } } [Range(0.0f, 1.0f)] [SerializeField] private float opacity = 1.0f; /// The angle of the paint in degrees. /// NOTE: This is only useful if you change the Scale.x/y values. public float Angle { set { angle = value; } get { return angle; } } [Range(-180.0f, 180.0f)] [SerializeField] private float angle; /// By default this component paints using a sphere shape, but you can override this here to paint an ellipsoid. /// NOTE: When painting an ellipsoid, the orientation of the sphere matters. This can be controlled from the P3dHit__ component settings. public Vector3 Scale { set { scale = value; } get { return scale; } } [SerializeField] private Vector3 scale = Vector3.one; /// The radius of the paint brush. public float Radius { set { radius = value; } get { return radius; } } [SerializeField] private float radius = 0.1f; /// The hardness of the paint brush. public float Hardness { set { hardness = value; } get { return hardness; } } [SerializeField] private float hardness = 5.0f; /// This allows you to apply a tiled detail texture to your decals. This tiling will be applied in world space using triplanar mapping. public Texture TileTexture { set { tileTexture = value; } get { return tileTexture; } } [SerializeField] private Texture tileTexture; /// This allows you to adjust the tiling position + rotation + scale using a Transform. public Transform TileTransform { set { tileTransform = value; } get { return tileTransform; } } [SerializeField] private Transform tileTransform; /// This allows you to control the triplanar influence. /// 0 = No influence. /// 1 = Full influence. public float TileOpacity { set { tileOpacity = value; } get { return tileOpacity; } } [UnityEngine.Serialization.FormerlySerializedAs("tileBlend")] [Range(0.0f, 1.0f)] [SerializeField] private float tileOpacity = 1.0f; /// This allows you to control how quickly the triplanar mapping transitions between the X/Y/Z planes. public float TileTransition { set { tileTransition = value; } get { return tileTransition; } } [Range(1.0f, 200.0f)] [SerializeField] private float tileTransition = 4.0f; /// This allows you to mask all paint that would otherwise apply to surfaces behind the specified depth mask rendered by a camera. public P3dRenderDepth DepthMask { set { depthMask = value; } get { return depthMask; } } [SerializeField] private P3dRenderDepth depthMask; /// This stores a list of all modifiers used to change the way this component applies paint (e.g. P3dModifyColorRandom). public P3dModifierList Modifiers { get { if (modifiers == null) modifiers = new P3dModifierList(); return modifiers; } } [SerializeField] private P3dModifierList modifiers; /// This method multiplies the radius by the specified value. public void IncrementOpacity(float delta) { opacity = Mathf.Clamp01(opacity + delta); } /// This method increments the angle by the specified amount of degrees, and wraps it to the -180..180 range. public void IncrementAngle(float degrees) { angle = Mathf.Repeat(angle + 180.0f + degrees, 360.0f) - 180.0f; } /// This method multiplies the radius by the specified value. public void MultiplyRadius(float multiplier) { radius *= multiplier; } /// This method multiplies the scale by the specified value. public void MultiplyScale(float multiplier) { scale *= multiplier; } /// This method paints all pixels at the specified point using the shape of a sphere. public void HandleHitPoint(bool preview, int priority, float pressure, int seed, Vector3 position, Quaternion rotation) { if (modifiers != null && modifiers.Count > 0) { CwHelper.BeginSeed(seed); modifiers.ModifyPosition(ref position, preview, pressure); CwHelper.EndSeed(); } P3dCommandSphere.Instance.SetState(preview, priority); P3dCommandSphere.Instance.SetLocation(position); var worldSize = HandleHitCommon(preview, pressure, seed, rotation); var worldRadius = P3dCommon.GetRadius(worldSize); var worldPosition = position; HandleMaskCommon(worldPosition); P3dPaintableManager.SubmitAll(P3dCommandSphere.Instance, worldPosition, worldRadius, layers, group, targetModel, targetTexture); } /// This method paints all pixels between the two specified points using the shape of a sphere. public void HandleHitLine(bool preview, int priority, float pressure, int seed, Vector3 position, Vector3 endPosition, Quaternion rotation, bool clip) { P3dCommandSphere.Instance.SetState(preview, priority); P3dCommandSphere.Instance.SetLocation(position, endPosition, clip: clip); var worldSize = HandleHitCommon(preview, pressure, seed, rotation); var worldRadius = P3dCommon.GetRadius(worldSize, position, endPosition); var worldPosition = P3dCommon.GetPosition(position, endPosition); HandleMaskCommon(worldPosition); P3dPaintableManager.SubmitAll(P3dCommandSphere.Instance, worldPosition, worldRadius, layers, group, targetModel, targetTexture); } /// This method paints all pixels between three points using the shape of a sphere. public void HandleHitTriangle(bool preview, int priority, float pressure, int seed, Vector3 positionA, Vector3 positionB, Vector3 positionC, Quaternion rotation) { P3dCommandSphere.Instance.SetState(preview, priority); P3dCommandSphere.Instance.SetLocation(positionA, positionB, positionC); var worldSize = HandleHitCommon(preview, pressure, seed, rotation); var worldRadius = P3dCommon.GetRadius(worldSize, positionA, positionB, positionC); var worldPosition = P3dCommon.GetPosition(positionA, positionB, positionC); HandleMaskCommon(worldPosition); P3dPaintableManager.SubmitAll(P3dCommandSphere.Instance, worldPosition, worldRadius, layers, group, targetModel, targetTexture); } /// This method paints all pixels between two pairs of points using the shape of a sphere. public void HandleHitQuad(bool preview, int priority, float pressure, int seed, Vector3 position, Vector3 endPosition, Vector3 position2, Vector3 endPosition2, Quaternion rotation, bool clip) { P3dCommandSphere.Instance.SetState(preview, priority); P3dCommandSphere.Instance.SetLocation(position, endPosition, position2, endPosition2, clip: clip); var worldSize = HandleHitCommon(preview, pressure, seed, rotation); var worldRadius = P3dCommon.GetRadius(worldSize, position, endPosition, position2, endPosition2); var worldPosition = P3dCommon.GetPosition(position, endPosition, position2, endPosition2); HandleMaskCommon(worldPosition); P3dPaintableManager.SubmitAll(P3dCommandSphere.Instance, worldPosition, worldRadius, layers, group, targetModel, targetTexture); } /// This method paints the scene using the current component settings at the specified P3dHit. /// NOTE: The rotation argument is in world space, where Quaternion.identity means the paint faces forward on the +Z axis, and up is +Y. public void HandleHitCoord(bool preview, int priority, float pressure, int seed, P3dHit hit, Quaternion rotation) { var model = hit.Transform.GetComponent(); if (model != null) { var paintableTextures = P3dPaintableTexture.FilterAll(model, group); for (var i = paintableTextures.Count - 1; i >= 0; i--) { var paintableTexture = paintableTextures[i]; var coord = paintableTexture.GetCoord(ref hit); if (modifiers != null && modifiers.Count > 0) { var position = (Vector3)coord; CwHelper.BeginSeed(seed); modifiers.ModifyPosition(ref position, preview, pressure); CwHelper.EndSeed(); coord = position; } P3dCommandSphere.Instance.SetState(preview, priority); P3dCommandSphere.Instance.SetLocation(coord, in3D: false); HandleHitCommon(preview, pressure, seed, rotation); P3dCommandSphere.Instance.ClearMask(); P3dCommandSphere.Instance.ApplyAspect(paintableTexture.Current); P3dPaintableManager.Submit(P3dCommandSphere.Instance, model, paintableTexture); } } } private Vector3 HandleHitCommon(bool preview, float pressure, int seed, Quaternion rotation) { var finalOpacity = opacity; var finalRadius = radius; var finalScale = scale; var finalHardness = hardness; var finalColor = color; var finalAngle = angle; var finalTileMatrix = tileTransform != null ? tileTransform.localToWorldMatrix : Matrix4x4.identity; if (modifiers != null && modifiers.Count > 0) { CwHelper.BeginSeed(seed); modifiers.ModifyColor(ref finalColor, preview, pressure); modifiers.ModifyAngle(ref finalAngle, preview, pressure); modifiers.ModifyOpacity(ref finalOpacity, preview, pressure); modifiers.ModifyRadius(ref finalRadius, preview, pressure); modifiers.ModifyScale(ref finalScale, preview, pressure); modifiers.ModifyHardness(ref finalHardness, preview, pressure); CwHelper.EndSeed(); } var finalSize = finalScale * finalRadius; P3dCommandSphere.Instance.SetShape(rotation, finalSize, finalAngle); P3dCommandSphere.Instance.SetMaterial(BlendMode, finalHardness, finalColor, finalOpacity, tileTexture, finalTileMatrix, tileOpacity, tileTransition); return finalSize; } private void HandleMaskCommon(Vector3 worldPosition) { var mask = P3dMask.Find(worldPosition, layers); if (mask != null) { P3dCommandSphere.Instance.SetMask(mask.Matrix, mask.Texture, mask.Channel, mask.Stretch); } else { P3dCommandSphere.Instance.ClearMask(); } P3dCommandSphere.Instance.DepthMask = depthMask; } #if UNITY_EDITOR protected virtual void OnDrawGizmosSelected() { Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, scale); Gizmos.DrawWireSphere(Vector3.zero, radius); } #endif } } #if UNITY_EDITOR namespace PaintIn3D { using UnityEditor; using TARGET = P3dPaintSphere; [CanEditMultipleObjects] [CustomEditor(typeof(TARGET))] public class P3dClickToPaintSphere_Editor : CwEditor { protected override void OnInspector() { TARGET tgt; TARGET[] tgts; GetTargets(out tgt, out tgts); BeginError(Any(tgts, t => t.Layers == 0 && t.TargetModel == null)); Draw("layers", "Only the P3dModel/P3dPaintable GameObjects whose layers are within this mask will be eligible for painting."); EndError(); Draw("group", "Only the P3dPaintableTexture components with a matching group will be painted by this component."); Separator(); Draw("blendMode", "This allows you to choose how the paint from this component will combine with the existing pixels of the textures you paint.\n\nNOTE: See the Blend Mode section of the documentation for more information."); Draw("color", "The color of the paint."); Draw("opacity", "The opacity of the brush."); Separator(); Draw("angle", "The angle of the paint in degrees.\n\nNOTE: This is only useful if you change the Scale.x/y values."); Draw("scale", "By default this component paints using a sphere shape, but you can override this here to paint an ellipsoid.\n\nNOTE: When painting an ellipsoid, the orientation of the sphere matters. This can be controlled from the P3dHit__ component settings."); Draw("radius", "The radius of the paint brush."); Draw("hardness", "This allows you to control the sharpness of the near+far depth cut-off point."); Separator(); if (DrawFoldout("Advanced", "Show advanced settings?") == true) { BeginIndent(); Draw("targetModel", "If this is set, then only the specified P3dModel/P3dPaintable will be painted, regardless of the layer setting."); Draw("targetTexture", "If this is set, then only the specified P3dPaintableTexture will be painted, regardless of the layer or group setting."); Separator(); Draw("tileTexture", "This allows you to apply a tiled detail texture to your decals. This tiling will be applied in world space using triplanar mapping."); Draw("tileTransform", "This allows you to adjust the tiling position + rotation + scale using a Transform."); Draw("tileOpacity", "This allows you to control the triplanar influence.\n\n0 = No influence.\n\n1 = Full influence."); Draw("tileTransition", "This allows you to control how quickly the triplanar mapping transitions between the X/Y/Z planes."); Draw("depthMask", "This allows you to mask all paint that would otherwise apply to surfaces behind the specified depth mask rendered by a camera."); EndIndent(); } Separator(); tgt.Modifiers.DrawEditorLayout(serializedObject, target, "Color", "Opacity", "Radius", "Scale", "Hardness", "Position"); } } } #endif