Tanoda
InkCanvas.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Linq;
5using UnityEngine;
6using UnityEngine.SceneManagement;
7#if UNITY_EDITOR
8using System.IO;
9using UnityEditor;
10using UnityEditor.SceneManagement;
11
12#endif
13
14namespace Es.InkPainter
15{
20 [RequireComponent(typeof(Renderer))]
21 [DisallowMultipleComponent]
22 public class InkCanvas : MonoBehaviour
23 {
24 [Serializable]
25 public class PaintSet
26 {
30 [HideInInspector] [NonSerialized] public Material material;
31
32 [SerializeField] [Tooltip("The property name of the main texture.")]
33 public string mainTextureName = "_MainTex";
34
35 [SerializeField] [Tooltip("Normal map texture property name.")]
36 public string normalTextureName = "_BumpMap";
37
38 [SerializeField] [Tooltip("The property name of the heightmap texture.")]
39 public string heightTextureName = "_ParallaxMap";
40
41 [SerializeField] [Tooltip("Whether or not use main texture paint.")]
42 public bool useMainPaint = true;
43
44 [SerializeField] [Tooltip("Whether or not use normal map paint (you need material on normal maps).")]
45 public bool useNormalPaint;
46
47 [SerializeField] [Tooltip("Whether or not use heightmap painting (you need material on the heightmap).")]
48 public bool useHeightPaint;
49
53 [HideInInspector] [NonSerialized] public Texture mainTexture;
54
58 [HideInInspector] [NonSerialized] public RenderTexture paintMainTexture;
59
63 [HideInInspector] [NonSerialized] public Texture normalTexture;
64
68 [HideInInspector] [NonSerialized] public RenderTexture paintNormalTexture;
69
73 [HideInInspector] [NonSerialized] public Texture heightTexture;
74
78 [HideInInspector] [NonSerialized] public RenderTexture paintHeightTexture;
79
80 #region ShaderPropertyID
81
82 [HideInInspector] [NonSerialized] public int mainTexturePropertyID;
83
84 [HideInInspector] [NonSerialized] public int normalTexturePropertyID;
85
86 [HideInInspector] [NonSerialized] public int heightTexturePropertyID;
87
88 #endregion ShaderPropertyID
89
90 #region Constractor
91
95 public PaintSet()
96 {
97 mainTextureName = "_MainTex";
98 useMainPaint = true;
99 }
100
112 {
113 this.mainTextureName = mainTextureName;
114 this.normalTextureName = normalTextureName;
115 this.heightTextureName = heightTextureName;
116 this.useMainPaint = useMainPaint;
117 this.useNormalPaint = useNormalPaint;
118 this.useHeightPaint = useHeightPaint;
119 }
120
132 bool useMainPaint, bool useNormalPaint, bool useHeightPaint, Material material)
135 {
136 this.material = material;
137 }
138
139 #endregion Constractor
140 }
141
142 private static Material paintMainMaterial;
143 private static Material paintNormalMaterial;
144 private static Material paintHeightMaterial;
145 private bool eraseFlag;
146 private RenderTexture debugEraserMainView;
147 private RenderTexture debugEraserNormalView;
148 private RenderTexture debugEraserHeightView;
149 private Bounds myBounds;
150#pragma warning disable 0649
151 private bool eraserDebug;
152#pragma warning restore 0649
153
157 public List<PaintSet> PaintDatas
158 {
159 get => paintSet;
160 set => paintSet = value;
161 }
162
166 public event Action<InkCanvas> OnCanvasAttached;
167
171 public event Action<InkCanvas> OnInitializedStart;
172
176 public event Action<InkCanvas> OnInitializedAfter;
177
181 public event Action<InkCanvas, Brush> OnPaintStart;
182
186 public event Action<InkCanvas> OnPaintEnd;
187
188 #region SerializedField
189
190 [SerializeField] private List<PaintSet> paintSet;
191
192 #endregion SerializedField
193
194 #region ShaderPropertyID
195
196 private int paintUVPropertyID;
197
198 private int brushTexturePropertyID;
199 private int brushScalePropertyID;
200 private int brushRotatePropertyID;
201 private int brushColorPropertyID;
202 private int brushNormalTexturePropertyID;
203 private int brushNormalBlendPropertyID;
204 private int brushHeightTexturePropertyID;
205 private int brushHeightBlendPropertyID;
206 private int brushHeightColorPropertyID;
207
208 #endregion ShaderPropertyID
209
210 #region ShaderKeywords
211
212 private const string COLOR_BLEND_USE_CONTROL = "INK_PAINTER_COLOR_BLEND_USE_CONTROL";
213 private const string COLOR_BLEND_USE_BRUSH = "INK_PAINTER_COLOR_BLEND_USE_BRUSH";
214 private const string COLOR_BLEND_NEUTRAL = "INK_PAINTER_COLOR_BLEND_NEUTRAL";
215 private const string COLOR_BLEND_ALPHA_ONLY = "INK_PAINTER_COLOR_BLEND_ALPHA_ONLY";
216
217 private const string NORMAL_BLEND_USE_BRUSH = "INK_PAINTER_NORMAL_BLEND_USE_BRUSH";
218 private const string NORMAL_BLEND_ADD = "INK_PAINTER_NORMAL_BLEND_ADD";
219 private const string NORMAL_BLEND_SUB = "INK_PAINTER_NORMAL_BLEND_SUB";
220 private const string NORMAL_BLEND_MIN = "INK_PAINTER_NORMAL_BLEND_MIN";
221 private const string NORMAL_BLEND_MAX = "INK_PAINTER_NORMAL_BLEND_MAX";
222 private const string DXT5NM_COMPRESS_USE = "DXT5NM_COMPRESS_USE";
223 private const string DXT5NM_COMPRESS_UNUSE = "DXT5NM_COMPRESS_UNUSE";
224
225 private const string HEIGHT_BLEND_USE_BRUSH = "INK_PAINTER_HEIGHT_BLEND_USE_BRUSH";
226 private const string HEIGHT_BLEND_ADD = "INK_PAINTER_HEIGHT_BLEND_ADD";
227 private const string HEIGHT_BLEND_SUB = "INK_PAINTER_HEIGHT_BLEND_SUB";
228 private const string HEIGHT_BLEND_MIN = "INK_PAINTER_HEIGHT_BLEND_MIN";
229 private const string HEIGHT_BLEND_MAX = "INK_PAINTER_HEIGHT_BLEND_MAX";
230 private const string HEIGHT_BLEND_COLOR_RGB_HEIGHT_A = "INK_PAINTER_HEIGHT_BLEND_COLOR_RGB_HEIGHT_A";
231
232 #endregion ShaderKeywords
233
234 #region MeshData
235
236 private MeshOperator meshOperator;
237
239 {
240 get
241 {
242 if (meshOperator == null)
243 Debug.LogError(
244 "To take advantage of the features must Mesh filter or Skinned mesh renderer component associated Mesh.");
245
246 return meshOperator;
247 }
248 }
249
250 #endregion MeshData
251
252 #region UnityEventMethod
253
254 private void Awake()
255 {
256 if (OnCanvasAttached != null)
257 OnCanvasAttached(this);
258 InitPropertyID();
259 SetMaterial();
260 SetTexture();
261 MeshDataCache();
262 }
263
264 private void Start()
265 {
266 if (OnInitializedStart != null)
267 OnInitializedStart(this);
268 SetRenderTexture();
269 if (OnInitializedAfter != null)
270 OnInitializedAfter(this);
271 }
272
273 private void OnDestroy()
274 {
275 //Debug.Log("InkCanvas has been destroyed.");
276 ReleaseRenderTexture();
277 }
278
279 private void OnGUI()
280 {
281 if (eraserDebug)
282 {
283 if (debugEraserMainView != null)
284 GUI.DrawTexture(new Rect(0, 0, 100, 100), debugEraserMainView);
285 if (debugEraserNormalView != null)
286 GUI.DrawTexture(new Rect(0, 100, 100, 100), debugEraserNormalView);
287 if (debugEraserHeightView != null)
288 GUI.DrawTexture(new Rect(0, 200, 100, 100), debugEraserHeightView);
289 }
290 }
291
292 #endregion UnityEventMethod
293
294 #region PrivateMethod
295
299 private void MeshDataCache()
300 {
301 var meshFilter = GetComponent<MeshFilter>();
302 var skinnedMeshRenderer = GetComponent<SkinnedMeshRenderer>();
303 if (meshFilter != null)
304 {
305 meshOperator = new MeshOperator(meshFilter.sharedMesh);
306 myBounds = meshFilter.sharedMesh.bounds;
307 }
308 else if (skinnedMeshRenderer != null)
309 {
310 meshOperator = new MeshOperator(skinnedMeshRenderer.sharedMesh);
311 }
312 else
313 {
314 Debug.LogWarning(
315 "Sometimes if the MeshFilter or SkinnedMeshRenderer does not exist in the component part does not work correctly.");
316 }
317 }
318
322 private void InitPropertyID()
323 {
324 if (paintSet == null || paintSet.Count == 0)
325 {
326 var m = GetComponent<Renderer>().materials;
327 paintSet = new List<PaintSet>
328 {
329 new PaintSet()
330 };
331 for (var i = 1; i < m.Length; i++) paintSet.Add(new PaintSet());
332 }
333
334 foreach (var p in paintSet)
335 {
336 p.mainTexturePropertyID = Shader.PropertyToID(p.mainTextureName);
337 p.normalTexturePropertyID = Shader.PropertyToID(p.normalTextureName);
338 p.heightTexturePropertyID = Shader.PropertyToID(p.heightTextureName);
339 }
340
341 paintUVPropertyID = Shader.PropertyToID("_PaintUV");
342 brushTexturePropertyID = Shader.PropertyToID("_Brush");
343 brushScalePropertyID = Shader.PropertyToID("_BrushScale");
344 brushRotatePropertyID = Shader.PropertyToID("_BrushRotate");
345 brushColorPropertyID = Shader.PropertyToID("_ControlColor");
346 brushNormalTexturePropertyID = Shader.PropertyToID("_BrushNormal");
347 brushNormalBlendPropertyID = Shader.PropertyToID("_NormalBlend");
348 brushHeightTexturePropertyID = Shader.PropertyToID("_BrushHeight");
349 brushHeightBlendPropertyID = Shader.PropertyToID("_HeightBlend");
350 brushHeightColorPropertyID = Shader.PropertyToID("_Color");
351 }
352
356 private void SetMaterial()
357 {
358 if (paintMainMaterial == null)
359 paintMainMaterial = new Material(Resources.Load<Material>("Es.InkPainter.PaintMain"));
360 if (paintNormalMaterial == null)
361 paintNormalMaterial = new Material(Resources.Load<Material>("Es.InkPainter.PaintNormal"));
362 if (paintHeightMaterial == null)
363 paintHeightMaterial = new Material(Resources.Load<Material>("Es.InkPainter.PaintHeight"));
364 var m = GetComponent<Renderer>().materials;
365 for (var i = 0; i < m.Length; ++i)
366 if (paintSet[i].material == null)
367 paintSet[i].material = m[i];
368 }
369
373 private void SetTexture()
374 {
375 foreach (var p in paintSet)
376 {
377 if (p.material.HasProperty(p.mainTexturePropertyID))
378 p.mainTexture = p.material.GetTexture(p.mainTexturePropertyID);
379 if (p.material.HasProperty(p.normalTexturePropertyID))
380 p.normalTexture = p.material.GetTexture(p.normalTexturePropertyID);
381 if (p.material.HasProperty(p.heightTexturePropertyID))
382 p.heightTexture = p.material.GetTexture(p.heightTexturePropertyID);
383 }
384 }
385
392 private RenderTexture SetupRenderTexture(Texture baseTex, int propertyID, Material material)
393 {
394 var rt = new RenderTexture(baseTex.width, baseTex.height, 0, RenderTextureFormat.ARGB32,
395 RenderTextureReadWrite.Linear);
396 rt.filterMode = baseTex.filterMode;
397 Graphics.Blit(baseTex, rt);
398 material.SetTexture(propertyID, rt);
399 return rt;
400 }
401
405 private void SetRenderTexture()
406 {
407 foreach (var p in paintSet)
408 {
409 if (p.useMainPaint)
410 if (p.mainTexture != null)
411 p.paintMainTexture = SetupRenderTexture(p.mainTexture, p.mainTexturePropertyID, p.material);
412 else
413 Debug.LogWarning(
414 "To take advantage of the main texture paint must set main texture to materials.");
415 if (p.useNormalPaint)
416 if (p.normalTexture != null)
417 p.paintNormalTexture =
418 SetupRenderTexture(p.normalTexture, p.normalTexturePropertyID, p.material);
419 else
420 Debug.LogWarning("To take advantage of the normal map paint must set normal map to materials.");
421 if (p.useHeightPaint)
422 if (p.heightTexture != null)
423 p.paintHeightTexture =
424 SetupRenderTexture(p.heightTexture, p.heightTexturePropertyID, p.material);
425 else
426 Debug.LogWarning("To take advantage of the height map paint must set height map to materials.");
427 }
428 }
429
433 private void ReleaseRenderTexture()
434 {
435 foreach (var p in paintSet)
436 {
437 if (RenderTexture.active != p.paintMainTexture && p.paintMainTexture != null &&
438 p.paintMainTexture.IsCreated())
439 p.paintMainTexture.Release();
440 if (RenderTexture.active != p.paintNormalTexture && p.paintNormalTexture != null &&
441 p.paintNormalTexture.IsCreated())
442 p.paintNormalTexture.Release();
443 if (RenderTexture.active != p.paintHeightTexture && p.paintHeightTexture != null &&
444 p.paintHeightTexture.IsCreated())
445 p.paintHeightTexture.Release();
446 }
447 }
448
454 private void SetPaintMainData(Brush brush, Vector2 uv)
455 {
456 paintMainMaterial.SetVector(paintUVPropertyID, uv);
457 paintMainMaterial.SetTexture(brushTexturePropertyID, brush.BrushTexture);
458 paintMainMaterial.SetFloat(brushScalePropertyID, brush.Scale);
459 paintMainMaterial.SetFloat(brushRotatePropertyID, brush.RotateAngle);
460 paintMainMaterial.SetVector(brushColorPropertyID, brush.Color);
461
462 foreach (var key in paintMainMaterial.shaderKeywords)
463 paintMainMaterial.DisableKeyword(key);
464 switch (brush.ColorBlending)
465 {
466 case Brush.ColorBlendType.UseColor:
467 paintMainMaterial.EnableKeyword(COLOR_BLEND_USE_CONTROL);
468 break;
469
470 case Brush.ColorBlendType.UseBrush:
471 paintMainMaterial.EnableKeyword(COLOR_BLEND_USE_BRUSH);
472 break;
473
474 case Brush.ColorBlendType.Neutral:
475 paintMainMaterial.EnableKeyword(COLOR_BLEND_NEUTRAL);
476 break;
477
478 case Brush.ColorBlendType.AlphaOnly:
479 paintMainMaterial.EnableKeyword(COLOR_BLEND_ALPHA_ONLY);
480 break;
481
482 default:
483 paintMainMaterial.EnableKeyword(COLOR_BLEND_USE_CONTROL);
484 break;
485 }
486 }
487
493 private void SetPaintNormalData(Brush brush, Vector2 uv, bool erase)
494 {
495 paintNormalMaterial.SetVector(paintUVPropertyID, uv);
496 paintNormalMaterial.SetTexture(brushTexturePropertyID, brush.BrushTexture);
497 paintNormalMaterial.SetTexture(brushNormalTexturePropertyID, brush.BrushNormalTexture);
498 paintNormalMaterial.SetFloat(brushScalePropertyID, brush.Scale);
499 paintNormalMaterial.SetFloat(brushRotatePropertyID, brush.RotateAngle);
500 paintNormalMaterial.SetFloat(brushNormalBlendPropertyID, brush.NormalBlend);
501
502 foreach (var key in paintNormalMaterial.shaderKeywords)
503 paintNormalMaterial.DisableKeyword(key);
504 switch (brush.NormalBlending)
505 {
506 case Brush.NormalBlendType.UseBrush:
507 paintNormalMaterial.EnableKeyword(NORMAL_BLEND_USE_BRUSH);
508 break;
509
510 case Brush.NormalBlendType.Add:
511 paintNormalMaterial.EnableKeyword(NORMAL_BLEND_ADD);
512 break;
513
514 case Brush.NormalBlendType.Sub:
515 paintNormalMaterial.EnableKeyword(NORMAL_BLEND_SUB);
516 break;
517
518 case Brush.NormalBlendType.Min:
519 paintNormalMaterial.EnableKeyword(NORMAL_BLEND_MIN);
520 break;
521
522 case Brush.NormalBlendType.Max:
523 paintNormalMaterial.EnableKeyword(NORMAL_BLEND_MAX);
524 break;
525
526 default:
527 paintNormalMaterial.EnableKeyword(NORMAL_BLEND_USE_BRUSH);
528 break;
529 }
530
531 switch (erase)
532 {
533 case true:
534 paintNormalMaterial.EnableKeyword(DXT5NM_COMPRESS_UNUSE);
535 break;
536 case false:
537 paintNormalMaterial.EnableKeyword(DXT5NM_COMPRESS_USE);
538 break;
539 }
540 }
541
547 private void SetPaintHeightData(Brush brush, Vector2 uv)
548 {
549 paintHeightMaterial.SetVector(paintUVPropertyID, uv);
550 paintHeightMaterial.SetTexture(brushTexturePropertyID, brush.BrushTexture);
551 paintHeightMaterial.SetTexture(brushHeightTexturePropertyID, brush.BrushHeightTexture);
552 paintHeightMaterial.SetFloat(brushScalePropertyID, brush.Scale);
553 paintHeightMaterial.SetFloat(brushRotatePropertyID, brush.RotateAngle);
554 paintHeightMaterial.SetFloat(brushHeightBlendPropertyID, brush.HeightBlend);
555 paintHeightMaterial.SetVector(brushHeightColorPropertyID, brush.Color);
556
557 foreach (var key in paintHeightMaterial.shaderKeywords)
558 paintHeightMaterial.DisableKeyword(key);
559 switch (brush.HeightBlending)
560 {
561 case Brush.HeightBlendType.UseBrush:
562 paintHeightMaterial.EnableKeyword(HEIGHT_BLEND_USE_BRUSH);
563 break;
564
565 case Brush.HeightBlendType.Add:
566 paintHeightMaterial.EnableKeyword(HEIGHT_BLEND_ADD);
567 break;
568
569 case Brush.HeightBlendType.Sub:
570 paintHeightMaterial.EnableKeyword(HEIGHT_BLEND_SUB);
571 break;
572
573 case Brush.HeightBlendType.Min:
574 paintHeightMaterial.EnableKeyword(HEIGHT_BLEND_MIN);
575 break;
576
577 case Brush.HeightBlendType.Max:
578 paintHeightMaterial.EnableKeyword(HEIGHT_BLEND_MAX);
579 break;
580
581 case Brush.HeightBlendType.ColorRGB_HeightA:
582 paintHeightMaterial.EnableKeyword(HEIGHT_BLEND_COLOR_RGB_HEIGHT_A);
583 break;
584
585 default:
586 paintHeightMaterial.EnableKeyword(HEIGHT_BLEND_ADD);
587 break;
588 }
589 }
590
601 private Brush GetEraser(Brush brush, PaintSet paintSet, Vector2 uv, bool useMainPaint, bool useNormalPaint,
602 bool useHeightpaint)
603 {
604 var b = brush.Clone() as Brush;
605 b.Color = Color.white;
606 b.ColorBlending = Brush.ColorBlendType.UseBrush;
607 b.NormalBlending = Brush.NormalBlendType.UseBrush;
608 b.HeightBlending = Brush.HeightBlendType.UseBrush;
609 b.NormalBlend = 1f;
610 b.HeightBlend = 1f;
611
612 if (useMainPaint)
613 {
614 var rt = RenderTexture.GetTemporary(brush.BrushTexture.width, brush.BrushTexture.height);
615 GrabArea.Clip(brush.BrushTexture, brush.Scale, paintSet.mainTexture, uv, brush.RotateAngle,
616 GrabArea.GrabTextureWrapMode.Clamp, rt);
617 b.BrushTexture = rt;
618 }
619
620 if (useNormalPaint)
621 {
622 var rt = RenderTexture.GetTemporary(brush.BrushNormalTexture.width, brush.BrushNormalTexture.height);
623 GrabArea.Clip(brush.BrushNormalTexture, brush.Scale, paintSet.normalTexture, uv, brush.RotateAngle,
624 GrabArea.GrabTextureWrapMode.Clamp, rt, false);
625 b.BrushNormalTexture = rt;
626 }
627
628 if (useHeightpaint)
629 {
630 var rt = RenderTexture.GetTemporary(brush.BrushHeightTexture.width, brush.BrushHeightTexture.height);
631 GrabArea.Clip(brush.BrushHeightTexture, brush.Scale, paintSet.heightTexture, uv, brush.RotateAngle,
632 GrabArea.GrabTextureWrapMode.Clamp, rt, false);
633 b.BrushHeightTexture = rt;
634 }
635
636 if (eraserDebug)
637 {
638 if (debugEraserMainView == null && useMainPaint)
639 debugEraserMainView = new RenderTexture(b.BrushTexture.width, b.BrushTexture.height, 0);
640 if (debugEraserNormalView == null && useNormalPaint)
641 debugEraserNormalView =
642 new RenderTexture(b.BrushNormalTexture.width, b.BrushNormalTexture.height, 0);
643 if (debugEraserHeightView == null && useHeightpaint)
644 debugEraserHeightView =
645 new RenderTexture(b.BrushHeightTexture.width, b.BrushHeightTexture.height, 0);
646
647 if (useMainPaint)
648 Graphics.Blit(b.BrushTexture, debugEraserMainView);
649 if (useNormalPaint)
650 Graphics.Blit(b.BrushNormalTexture, debugEraserNormalView);
651 if (useHeightpaint)
652 Graphics.Blit(b.BrushHeightTexture, debugEraserHeightView);
653 }
654
655 return b;
656 }
657
665 private void ReleaseEraser(Brush brush, bool useMainPaint, bool useNormalPaint, bool useHeightpaint)
666 {
667 if (useMainPaint && brush.BrushTexture is RenderTexture)
668 RenderTexture.ReleaseTemporary(brush.BrushTexture as RenderTexture);
669
670 if (useNormalPaint && brush.BrushNormalTexture is RenderTexture)
671 RenderTexture.ReleaseTemporary(brush.BrushNormalTexture as RenderTexture);
672
673 if (useHeightpaint && brush.BrushHeightTexture is RenderTexture)
674 RenderTexture.ReleaseTemporary(brush.BrushHeightTexture as RenderTexture);
675 }
676
677 #endregion PrivateMethod
678
679 #region PublicMethod
680
687 public bool PaintUVDirect(Brush brush, Vector2 uv, Func<PaintSet, bool> materialSelector = null)
688 {
689 #region ErrorCheck
690
691 if (brush == null)
692 {
693 Debug.LogError("Do not set the brush.");
694 eraseFlag = false;
695 return false;
696 }
697
698 #endregion ErrorCheck
699
700 #region BrushSizeFixer
701
702 if (myBounds.size.x < 0.05f && myBounds.size.z < 0.05f && myBounds.size.y < 0.05f)
703 {
704 brush = brush.Clone() as Brush;
705 brush.Scale = 0.5f;
706 }
707
708 #endregion
709
710 if (OnPaintStart != null)
711 {
712 brush = brush.Clone() as Brush;
713 OnPaintStart(this, brush);
714 }
715
716 var set = materialSelector == null ? paintSet : paintSet.Where(materialSelector);
717 foreach (var p in set)
718 {
719 var mainPaintConditions = p.useMainPaint && brush.BrushTexture != null && p.paintMainTexture != null &&
720 p.paintMainTexture.IsCreated();
721 var normalPaintConditions = p.useNormalPaint && brush.BrushNormalTexture != null &&
722 p.paintNormalTexture != null && p.paintNormalTexture.IsCreated();
723 var heightPaintConditions = p.useHeightPaint && brush.BrushHeightTexture != null &&
724 p.paintHeightTexture != null && p.paintHeightTexture.IsCreated();
725
726 if (eraseFlag)
727 brush = GetEraser(brush, p, uv, mainPaintConditions, normalPaintConditions, heightPaintConditions);
728
729 if (mainPaintConditions)
730 {
731 var mainPaintTextureBuffer = RenderTexture.GetTemporary(p.paintMainTexture.width,
732 p.paintMainTexture.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
733 SetPaintMainData(brush, uv);
734 Graphics.Blit(p.paintMainTexture, mainPaintTextureBuffer, paintMainMaterial);
735 Graphics.Blit(mainPaintTextureBuffer, p.paintMainTexture);
736 RenderTexture.ReleaseTemporary(mainPaintTextureBuffer);
737 }
738
739 if (normalPaintConditions)
740 {
741 var normalPaintTextureBuffer = RenderTexture.GetTemporary(p.paintNormalTexture.width,
742 p.paintNormalTexture.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
743 SetPaintNormalData(brush, uv, eraseFlag);
744 Graphics.Blit(p.paintNormalTexture, normalPaintTextureBuffer, paintNormalMaterial);
745 Graphics.Blit(normalPaintTextureBuffer, p.paintNormalTexture);
746 RenderTexture.ReleaseTemporary(normalPaintTextureBuffer);
747 }
748
749 if (heightPaintConditions)
750 {
751 var heightPaintTextureBuffer = RenderTexture.GetTemporary(p.paintHeightTexture.width,
752 p.paintHeightTexture.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
753 SetPaintHeightData(brush, uv);
754 Graphics.Blit(p.paintHeightTexture, heightPaintTextureBuffer, paintHeightMaterial);
755 Graphics.Blit(heightPaintTextureBuffer, p.paintHeightTexture);
756 RenderTexture.ReleaseTemporary(heightPaintTextureBuffer);
757 }
758
759 if (eraseFlag)
760 ReleaseEraser(brush, mainPaintConditions, normalPaintConditions, heightPaintConditions);
761 }
762
763 if (OnPaintEnd != null)
764 OnPaintEnd(this);
765
766 eraseFlag = false;
767 return true;
768 }
769
777 public bool PaintNearestTriangleSurface(Brush brush, Vector3 worldPos,
778 Func<PaintSet, bool> materialSelector = null, Camera renderCamera = null)
779 {
780 var p = transform.worldToLocalMatrix.MultiplyPoint(worldPos);
782
783 return Paint(brush, transform.localToWorldMatrix.MultiplyPoint(pd), materialSelector, renderCamera);
784 }
785
793 public bool Paint(Brush brush, Vector3 worldPos, Func<PaintSet, bool> materialSelector = null,
794 Camera renderCamera = null)
795 {
796 Vector2 uv;
797
798 if (renderCamera == null)
799 renderCamera = Camera.main;
800
801 var p = transform.InverseTransformPoint(worldPos);
802 var mvp = renderCamera.projectionMatrix * renderCamera.worldToCameraMatrix * transform.localToWorldMatrix;
803 if (MeshOperator.LocalPointToUV(p, mvp, out uv))
804 {
805 return PaintUVDirect(brush, uv, materialSelector);
806 }
807
808 Debug.LogWarning("Could not get the point on the surface.");
809 return PaintNearestTriangleSurface(brush, worldPos, materialSelector, renderCamera);
810 }
811
812 public bool Paint(Brush brush, Vector3 localPos)
813 {
814 var renderCamera = Camera.main;
815
816 var mvp = renderCamera.projectionMatrix * renderCamera.worldToCameraMatrix * transform.localToWorldMatrix;
817 if (MeshOperator.LocalPointToUV(localPos, mvp, out var uv))
818 return PaintUVDirect(brush, uv, null);
819
820 return false;
821 }
822
830 public bool Paint(Brush brush, RaycastHit hitInfo, Func<PaintSet, bool> materialSelector = null)
831 {
832 if (hitInfo.collider != null)
833 {
834 if (hitInfo.collider is MeshCollider)
835 return PaintUVDirect(brush, hitInfo.textureCoord, materialSelector);
836 Debug.LogWarning("If you want to paint using a RaycastHit, need set MeshCollider for object.");
837 return PaintNearestTriangleSurface(brush, hitInfo.point, materialSelector);
838 }
839
840 return false;
841 }
842
849 public bool EraseUVDirect(Brush brush, Vector2 uv, Func<PaintSet, bool> materialSelector = null)
850 {
851 eraseFlag = true;
852 return PaintUVDirect(brush, uv, materialSelector);
853 }
854
862 public bool EraseNearestTriangleSurface(Brush brush, Vector3 worldPos,
863 Func<PaintSet, bool> materialSelector = null, Camera renderCamera = null)
864 {
865 eraseFlag = true;
866 return PaintNearestTriangleSurface(brush, worldPos, materialSelector, renderCamera);
867 }
868
876 public bool Erase(Brush brush, Vector3 worldPos, Func<PaintSet, bool> materialSelector = null,
877 Camera renderCamera = null)
878 {
879 eraseFlag = true;
880 return Paint(brush, worldPos, materialSelector, renderCamera);
881 }
882
890 public bool Erase(Brush brush, RaycastHit hitInfo, Func<PaintSet, bool> materialSelector = null)
891 {
892 eraseFlag = true;
893 return Paint(brush, hitInfo, materialSelector);
894 }
895
899 public void ResetPaint()
900 {
901 ReleaseRenderTexture();
902 SetRenderTexture();
903 if (OnInitializedAfter != null)
904 OnInitializedAfter(this);
905 }
906
912 public Texture GetMainTexture(string materialName)
913 {
914 materialName = materialName.Replace(" (Instance)", "");
915 var data = paintSet.FirstOrDefault(p => p.material.name.Replace(" (Instance)", "") == materialName);
916 if (data == null)
917 return null;
918 return data.mainTexture;
919 }
920
926 public RenderTexture GetPaintMainTexture(string materialName)
927 {
928 materialName = materialName.Replace(" (Instance)", "");
929 var data = paintSet.FirstOrDefault(p => p.material.name.Replace(" (Instance)", "") == materialName);
930 if (data == null)
931 return null;
932 return data.paintMainTexture;
933 }
934
940 public void SetPaintMainTexture(string materialName, RenderTexture newTexture)
941 {
942 materialName = materialName.Replace(" (Instance)", "");
943 var data = paintSet.FirstOrDefault(p => p.material.name.Replace(" (Instance)", "") == materialName);
944 if (data == null)
945 {
946 Debug.LogError("Failed to set texture.");
947 return;
948 }
949
950 data.paintMainTexture = newTexture;
951 data.material.SetTexture(data.mainTextureName, data.paintMainTexture);
952 data.useMainPaint = true;
953 }
954
960 public Texture GetNormalTexture(string materialName)
961 {
962 materialName = materialName.Replace(" (Instance)", "");
963 var data = paintSet.FirstOrDefault(p => p.material.name.Replace(" (Instance)", "") == materialName);
964 if (data == null)
965 return null;
966 return data.normalTexture;
967 }
968
974 public RenderTexture GetPaintNormalTexture(string materialName)
975 {
976 materialName = materialName.Replace(" (Instance)", "");
977 var data = paintSet.FirstOrDefault(p => p.material.name.Replace(" (Instance)", "") == materialName);
978 if (data == null)
979 return null;
980 return data.paintNormalTexture;
981 }
982
988 public void SetPaintNormalTexture(string materialName, RenderTexture newTexture)
989 {
990 materialName = materialName.Replace(" (Instance)", "");
991 var data = paintSet.FirstOrDefault(p => p.material.name.Replace(" (Instance)", "") == materialName);
992 if (data == null)
993 {
994 Debug.LogError("Failed to set texture.");
995 return;
996 }
997
998 data.paintNormalTexture = newTexture;
999 data.material.SetTexture(data.normalTextureName, data.paintNormalTexture);
1000 data.useNormalPaint = true;
1001 }
1002
1008 public Texture GetHeightTexture(string materialName)
1009 {
1010 materialName = materialName.Replace(" (Instance)", "");
1011 var data = paintSet.FirstOrDefault(p => p.material.name.Replace(" (Instance)", "") == materialName);
1012 if (data == null)
1013 return null;
1014 return data.heightTexture;
1015 }
1016
1022 public RenderTexture GetPaintHeightTexture(string materialName)
1023 {
1024 materialName = materialName.Replace(" (Instance)", "");
1025 var data = paintSet.FirstOrDefault(p => p.material.name.Replace(" (Instance)", "") == materialName);
1026 if (data == null)
1027 return null;
1028 return data.paintHeightTexture;
1029 }
1030
1036 public void SetPaintHeightTexture(string materialName, RenderTexture newTexture)
1037 {
1038 materialName = materialName.Replace(" (Instance)", "");
1039 var data = paintSet.FirstOrDefault(p => p.material.name.Replace(" (Instance)", "") == materialName);
1040 if (data == null)
1041 {
1042 Debug.LogError("Failed to set texture.");
1043 return;
1044 }
1045
1046 data.paintHeightTexture = newTexture;
1047 data.material.SetTexture(data.heightTextureName, data.paintHeightTexture);
1048 data.useHeightPaint = true;
1049 }
1050
1051 #endregion PublicMethod
1052
1053 #region CustomEditor
1054
1055#if UNITY_EDITOR
1056
1057 [CustomEditor(typeof(InkCanvas))]
1058 [CanEditMultipleObjects]
1059 private class InkCanvasInspectorExtension : Editor
1060 {
1061 private Renderer renderer;
1062 private Material[] materials;
1063 private List<bool> foldOut;
1064
1065 public override void OnInspectorGUI()
1066 {
1067 var instance = target as InkCanvas;
1068 if (instance.paintSet == null)
1069 instance.paintSet = new List<PaintSet>();
1070
1071 if (renderer == null)
1072 renderer = instance.GetComponent<Renderer>();
1073 if (materials == null)
1074 materials = renderer.sharedMaterials;
1075 if (foldOut == null)
1076 foldOut = new List<bool>();
1077
1078 if (instance.paintSet.Count < materials.Length)
1079 {
1080 for (var i = instance.paintSet.Count; i < materials.Length; ++i)
1081 instance.paintSet.Add(new PaintSet
1082 {
1083 mainTextureName = "_MainTex",
1084 normalTextureName = "_BumpMap",
1085 heightTextureName = "_ParallaxMap",
1086 useMainPaint = true,
1087 useNormalPaint = false,
1088 useHeightPaint = false
1089 });
1090 foldOut.Clear();
1091 }
1092
1093 if (instance.paintSet.Count > materials.Length)
1094 {
1095 instance.paintSet.RemoveRange(materials.Length, instance.paintSet.Count - materials.Length);
1096 foldOut.Clear();
1097 }
1098
1099 if (foldOut.Count < instance.paintSet.Count)
1100 for (var i = foldOut.Count; i < instance.paintSet.Count; ++i)
1101 foldOut.Add(true);
1102
1103 EditorGUILayout.Space();
1104
1105 if (EditorApplication.isPlaying)
1106 {
1107 #region PlayModeOperation
1108
1109 EditorGUILayout.HelpBox("Can not change while playing.\n but you can saved painted texture.",
1110 MessageType.Info);
1111 for (var i = 0; i < instance.paintSet.Count; ++i)
1112 if (foldOut[i] = Foldout(foldOut[i], string.Format("Material \"{0}\"", materials[i].name)))
1113 {
1114 EditorGUILayout.BeginVertical("ProgressBarBack");
1115 var backColorBuf = GUI.backgroundColor;
1116 GUI.backgroundColor = Color.green;
1117
1118 var paintSet = instance.paintSet[i];
1119
1120 if (paintSet.paintMainTexture != null && GUILayout.Button("Save main texture"))
1121 SaveRenderTextureToPNG(
1122 paintSet.mainTexture != null ? paintSet.mainTexture.name : "main_texture",
1123 paintSet.paintMainTexture);
1124
1125 if (instance.paintSet[i].paintNormalTexture != null &&
1126 GUILayout.Button("Save normal texture"))
1127 //TODO:https://github.com/EsProgram/InkPainter/issues/13
1128 SaveRenderTextureToPNG(
1129 paintSet.normalTexture != null ? paintSet.normalTexture.name : "normal_texture",
1130 paintSet.paintNormalTexture);
1131
1132 if (instance.paintSet[i].paintHeightTexture != null &&
1133 GUILayout.Button("Save height texture"))
1134 SaveRenderTextureToPNG(
1135 paintSet.heightTexture != null ? paintSet.heightTexture.name : "height_texture",
1136 paintSet.paintHeightTexture);
1137
1138 GUI.backgroundColor = backColorBuf;
1139 EditorGUILayout.EndVertical();
1140 }
1141
1142 EditorGUILayout.Space();
1143 instance.eraserDebug = EditorGUILayout.Toggle("Eracer debug option", instance.eraserDebug);
1144 if (instance.eraserDebug)
1145 {
1146 if (GUILayout.Button("Save eracer main texture"))
1147 SaveRenderTextureToPNG("eracer_main", instance.debugEraserMainView);
1148 if (GUILayout.Button("Save eracer normal texture"))
1149 SaveRenderTextureToPNG("eracer_normal", instance.debugEraserNormalView);
1150 if (GUILayout.Button("Save eracer height texture"))
1151 SaveRenderTextureToPNG("eracer_height", instance.debugEraserHeightView);
1152 }
1153
1154 #endregion PlayModeOperation
1155 }
1156 else
1157 {
1158 #region Property Setting
1159
1160 for (var i = 0; i < instance.paintSet.Count; ++i)
1161 if (foldOut[i] = Foldout(foldOut[i], string.Format("Material \"{0}\"", materials[i].name)))
1162 {
1163 EditorGUI.indentLevel = 0;
1164 EditorGUILayout.BeginVertical("ProgressBarBack");
1165
1166 //MainPaint
1167 EditorGUI.BeginChangeCheck();
1168 instance.paintSet[i].useMainPaint =
1169 EditorGUILayout.Toggle("Use Main Paint", instance.paintSet[i].useMainPaint);
1170 if (EditorGUI.EndChangeCheck())
1171 ChangeValue(i, "Use Main Paint",
1172 p => p.useMainPaint = instance.paintSet[i].useMainPaint);
1173 if (instance.paintSet[i].useMainPaint)
1174 {
1175 EditorGUI.indentLevel++;
1176 EditorGUI.BeginChangeCheck();
1177 instance.paintSet[i].mainTextureName =
1178 EditorGUILayout.TextField("MainTexture Property Name",
1179 instance.paintSet[i].mainTextureName);
1180 if (EditorGUI.EndChangeCheck())
1181 ChangeValue(i, "Main Texture Name",
1182 p => p.mainTextureName = instance.paintSet[i].mainTextureName);
1183 EditorGUI.indentLevel--;
1184 }
1185
1186 //NormalPaint
1187 EditorGUI.BeginChangeCheck();
1188 instance.paintSet[i].useNormalPaint = EditorGUILayout.Toggle("Use NormalMap Paint",
1189 instance.paintSet[i].useNormalPaint);
1190 if (EditorGUI.EndChangeCheck())
1191 ChangeValue(i, "Use Normal Paint",
1192 p => p.useNormalPaint = instance.paintSet[i].useNormalPaint);
1193 if (instance.paintSet[i].useNormalPaint)
1194 {
1195 EditorGUI.indentLevel++;
1196 EditorGUI.BeginChangeCheck();
1197 instance.paintSet[i].normalTextureName =
1198 EditorGUILayout.TextField("NormalMap Property Name",
1199 instance.paintSet[i].normalTextureName);
1200 if (EditorGUI.EndChangeCheck())
1201 ChangeValue(i, "Normal Texture Name",
1202 p => p.normalTextureName = instance.paintSet[i].normalTextureName);
1203 EditorGUI.indentLevel--;
1204 }
1205
1206 //HeightPaint
1207 EditorGUI.BeginChangeCheck();
1208 instance.paintSet[i].useHeightPaint = EditorGUILayout.Toggle("Use HeightMap Paint",
1209 instance.paintSet[i].useHeightPaint);
1210 if (EditorGUI.EndChangeCheck())
1211 ChangeValue(i, "Use Height Paint",
1212 p => p.useHeightPaint = instance.paintSet[i].useHeightPaint);
1213 if (instance.paintSet[i].useHeightPaint)
1214 {
1215 EditorGUI.indentLevel++;
1216 EditorGUI.BeginChangeCheck();
1217 instance.paintSet[i].heightTextureName =
1218 EditorGUILayout.TextField("HeightMap Property Name",
1219 instance.paintSet[i].heightTextureName);
1220 if (EditorGUI.EndChangeCheck())
1221 ChangeValue(i, "Height Texture Name",
1222 p => p.heightTextureName = instance.paintSet[i].heightTextureName);
1223 EditorGUI.indentLevel--;
1224 }
1225
1226 EditorGUILayout.EndVertical();
1227 EditorGUI.indentLevel = 0;
1228 }
1229
1230 #endregion Property Setting
1231 }
1232 }
1233
1234 private void SaveRenderTextureToPNG(string textureName, RenderTexture renderTexture,
1235 Action<TextureImporter> importAction = null)
1236 {
1237 var path = EditorUtility.SaveFilePanel("Save to png", Application.dataPath,
1238 textureName + "_painted.png", "png");
1239 if (path.Length != 0)
1240 {
1241 var newTex = new Texture2D(renderTexture.width, renderTexture.height);
1242 RenderTexture.active = renderTexture;
1243 newTex.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
1244 newTex.Apply();
1245
1246 var pngData = newTex.EncodeToPNG();
1247 if (pngData != null)
1248 {
1249 File.WriteAllBytes(path, pngData);
1250 AssetDatabase.Refresh();
1251 var importer = AssetImporter.GetAtPath(path) as TextureImporter;
1252 if (importAction != null)
1253 importAction(importer);
1254 }
1255
1256 Debug.Log(path);
1257 }
1258 }
1259
1260 private void ChangeValue(int paintSetIndex, string recordName, Action<PaintSet> assign)
1261 {
1262 Undo.RecordObjects(targets, "Change " + recordName);
1263 foreach (var t in targets.Where(_t => _t is InkCanvas).Select(_t => _t as InkCanvas))
1264 if (t.paintSet.Count > paintSetIndex)
1265 {
1266 assign(t.paintSet[paintSetIndex]);
1267 EditorUtility.SetDirty(t);
1268 }
1269
1270 EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
1271 }
1272
1273 public bool Foldout(bool foldout, string content)
1274 {
1275 var style = new GUIStyle("ShurikenModuleTitle");
1276 style.font = new GUIStyle(EditorStyles.label).font;
1277 style.border = new RectOffset(1, 7, 4, 4);
1278 style.fixedHeight = 28;
1279 style.contentOffset = new Vector2(20f, -2f);
1280
1281 var rect = GUILayoutUtility.GetRect(16f, 22f, style);
1282 GUI.Box(rect, content, style);
1283
1284 var e = Event.current;
1285
1286 var toggleRect = new Rect(rect.x + 4f, rect.y + 5f, 13f, 13f);
1287 if (e.type == EventType.Repaint) EditorStyles.foldout.Draw(toggleRect, false, false, foldout, false);
1288
1289 if (e.type == EventType.MouseDown && rect.Contains(e.mousePosition))
1290 {
1291 foldout = !foldout;
1292 e.Use();
1293 }
1294
1295 return foldout;
1296 }
1297 }
1298
1299#endif
1300
1301 #endregion CustomEditor
1302 }
1303}
UnityEngine.Debug Debug
Definition: TanodaServer.cs:19
UnityEngine.Color Color
Definition: TestScript.cs:32
Class managing brush information.
Definition: Brush.cs:11
float Scale
The size of the brush. It takes a range from 0 to 1.
Definition: Brush.cs:159
object Clone()
Definition: Brush.cs:267
Texture mainTexture
In the first time set to the material's main texture.
Definition: InkCanvas.cs:53
RenderTexture paintNormalTexture
Copied the normal map to rendertexture that use to paint.
Definition: InkCanvas.cs:68
RenderTexture paintHeightTexture
Copied the height map to rendertexture that use to paint.
Definition: InkCanvas.cs:78
Material material
Applying paint materials.
Definition: InkCanvas.cs:30
PaintSet(string mainTextureName, string normalTextureName, string heightTextureName, bool useMainPaint, bool useNormalPaint, bool useHeightPaint)
Setup paint data.
Definition: InkCanvas.cs:110
PaintSet()
Default constractor.
Definition: InkCanvas.cs:95
Texture heightTexture
In the first time set to the material's height map.
Definition: InkCanvas.cs:73
PaintSet(string mainTextureName, string normalTextureName, string heightTextureName, bool useMainPaint, bool useNormalPaint, bool useHeightPaint, Material material)
Setup paint data.
Definition: InkCanvas.cs:131
RenderTexture paintMainTexture
Copied the main texture to rendertexture that use to paint.
Definition: InkCanvas.cs:58
Texture normalTexture
In the first time set to the material's normal map.
Definition: InkCanvas.cs:63
Texture paint to canvas. To set the per-material.
Definition: InkCanvas.cs:23
bool Paint(Brush brush, Vector3 localPos)
Definition: InkCanvas.cs:812
Action< InkCanvas, Brush > OnPaintStart
Called at paint start.
Definition: InkCanvas.cs:181
Action< InkCanvas > OnCanvasAttached
Called by InkCanvas attached game object.
Definition: InkCanvas.cs:166
bool Erase(Brush brush, Vector3 worldPos, Func< PaintSet, bool > materialSelector=null, Camera renderCamera=null)
Erase processing that use world-space surface position.
Definition: InkCanvas.cs:876
bool PaintNearestTriangleSurface(Brush brush, Vector3 worldPos, Func< PaintSet, bool > materialSelector=null, Camera renderCamera=null)
Paint of points close to the given world-space position on the Mesh surface.
Definition: InkCanvas.cs:777
bool PaintUVDirect(Brush brush, Vector2 uv, Func< PaintSet, bool > materialSelector=null)
Paint processing that UV coordinates to the specified.
Definition: InkCanvas.cs:687
Action< InkCanvas > OnInitializedAfter
Called by InkCanvas initialization completion times.
Definition: InkCanvas.cs:176
bool EraseNearestTriangleSurface(Brush brush, Vector3 worldPos, Func< PaintSet, bool > materialSelector=null, Camera renderCamera=null)
Erase of points close to the given world-space position on the Mesh surface.
Definition: InkCanvas.cs:862
List< PaintSet > PaintDatas
Access data used for painting.
Definition: InkCanvas.cs:158
void ResetPaint()
To reset the paint.
Definition: InkCanvas.cs:899
Action< InkCanvas > OnInitializedStart
Called by InkCanvas initialization start times.
Definition: InkCanvas.cs:171
MeshOperator MeshOperator
Definition: InkCanvas.cs:239
Texture GetMainTexture(string materialName)
To get the original main texture.
Definition: InkCanvas.cs:912
bool EraseUVDirect(Brush brush, Vector2 uv, Func< PaintSet, bool > materialSelector=null)
Erase processing that UV coordinates to the specified.
Definition: InkCanvas.cs:849
bool Paint(Brush brush, Vector3 worldPos, Func< PaintSet, bool > materialSelector=null, Camera renderCamera=null)
Paint processing that use world-space surface position.
Definition: InkCanvas.cs:793
bool Erase(Brush brush, RaycastHit hitInfo, Func< PaintSet, bool > materialSelector=null)
Erase processing that use raycast hit data. Must MeshCollider is set to the canvas.
Definition: InkCanvas.cs:890
RenderTexture GetPaintNormalTexture(string materialName)
To get the paint in normal map.
Definition: InkCanvas.cs:974
void SetPaintHeightTexture(string materialName, RenderTexture newTexture)
Set paint texture.
Definition: InkCanvas.cs:1036
void SetPaintMainTexture(string materialName, RenderTexture newTexture)
Set paint texture.
Definition: InkCanvas.cs:940
bool Paint(Brush brush, RaycastHit hitInfo, Func< PaintSet, bool > materialSelector=null)
Paint processing that use raycast hit data. Must MeshCollider is set to the canvas.
Definition: InkCanvas.cs:830
Action< InkCanvas > OnPaintEnd
Called at paint end.
Definition: InkCanvas.cs:186
RenderTexture GetPaintMainTexture(string materialName)
To get the main texture in paint.
Definition: InkCanvas.cs:926
Texture GetHeightTexture(string materialName)
To get the original height map.
Definition: InkCanvas.cs:1008
Texture GetNormalTexture(string materialName)
To get the original normal map.
Definition: InkCanvas.cs:960
RenderTexture GetPaintHeightTexture(string materialName)
To get the paint in height map.
Definition: InkCanvas.cs:1022
void SetPaintNormalTexture(string materialName, RenderTexture newTexture)
Set paint texture.
Definition: InkCanvas.cs:988
A class that manipulates Mesh.
Definition: MeshOperator.cs:12
bool LocalPointToUV(Vector3 localPoint, Matrix4x4 matrixMVP, out Vector2 uv)
Convert local-space point to texture coordinates.
Definition: MeshOperator.cs:52
Vector3 NearestLocalSurfacePoint(Vector3 localPoint)
Returns the point on the surface of Mesh closest to the point on the specified local-space.
Definition: MeshOperator.cs:94
Definition: ClipPainter.cs:5