﻿<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ja">
	<id>https://wiki.blender.jp/index.php?action=history&amp;feed=atom&amp;title=%E5%88%A9%E7%94%A8%E8%80%85%3ABitinat2%2FGSoC_2016%2FCodeDocs</id>
	<title>利用者:Bitinat2/GSoC 2016/CodeDocs - 版の履歴</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.blender.jp/index.php?action=history&amp;feed=atom&amp;title=%E5%88%A9%E7%94%A8%E8%80%85%3ABitinat2%2FGSoC_2016%2FCodeDocs"/>
	<link rel="alternate" type="text/html" href="https://wiki.blender.jp/index.php?title=%E5%88%A9%E7%94%A8%E8%80%85:Bitinat2/GSoC_2016/CodeDocs&amp;action=history"/>
	<updated>2026-08-21T02:50:52Z</updated>
	<subtitle>このウィキのこのページに関する変更履歴</subtitle>
	<generator>MediaWiki 1.31.0</generator>
	<entry>
		<id>https://wiki.blender.jp/index.php?title=%E5%88%A9%E7%94%A8%E8%80%85:Bitinat2/GSoC_2016/CodeDocs&amp;diff=152167&amp;oldid=prev</id>
		<title>Yamyam: 1版 をインポートしました</title>
		<link rel="alternate" type="text/html" href="https://wiki.blender.jp/index.php?title=%E5%88%A9%E7%94%A8%E8%80%85:Bitinat2/GSoC_2016/CodeDocs&amp;diff=152167&amp;oldid=prev"/>
		<updated>2018-06-28T21:21:05Z</updated>

		<summary type="html">&lt;p&gt;1版 をインポートしました&lt;/p&gt;
&lt;table class=&quot;diff diff-contentalign-left&quot; data-mw=&quot;interface&quot;&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;ja&quot;&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #222; text-align: center;&quot;&gt;← 古い版&lt;/td&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #222; text-align: center;&quot;&gt;2018年6月28日 (木) 21:21時点における版&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-notice&quot; lang=&quot;ja&quot;&gt;&lt;div class=&quot;mw-diff-empty&quot;&gt;(相違点なし)&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</summary>
		<author><name>Yamyam</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.blender.jp/index.php?title=%E5%88%A9%E7%94%A8%E8%80%85:Bitinat2/GSoC_2016/CodeDocs&amp;diff=152166&amp;oldid=prev</id>
		<title>wiki&gt;Bitinat2: /* Splash Prevention */</title>
		<link rel="alternate" type="text/html" href="https://wiki.blender.jp/index.php?title=%E5%88%A9%E7%94%A8%E8%80%85:Bitinat2/GSoC_2016/CodeDocs&amp;diff=152166&amp;oldid=prev"/>
		<updated>2016-08-16T00:16:41Z</updated>

		<summary type="html">&lt;p&gt;‎&lt;span dir=&quot;auto&quot;&gt;&lt;span class=&quot;autocomment&quot;&gt;Splash Prevention&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;新規ページ&lt;/b&gt;&lt;/p&gt;&lt;div&gt;=PBVH Painting Developer Documentation=&lt;br /&gt;
&lt;br /&gt;
==Things to Know==&lt;br /&gt;
&lt;br /&gt;
# I highly recommend reading my GSOC proposal if you haven't done so already. [[User:Bitinat2/GSoC_2016/Proposal|My proposal can be found here.]]&lt;br /&gt;
# Try to get familiar with what a PBVH is. [[Dev:Source/Modeling/PBVH|PBVH documentation can be found here]]&lt;br /&gt;
# As of 08/13/2016, much of this project reuses sculpting structs and methods. &lt;br /&gt;
#* If you ever see the word 'sculpt' show up in this project's code, just know that method or struct has been generalized to work with both sculpt mode and vertex/weight painting.&lt;br /&gt;
#* Being familiar with sculpt code isn't necessary, but could be extremely helpful in modifying vertex painting since both modes use PBVHs.&lt;br /&gt;
# This code takes a spherical based vertex selection approach as opposed to the previous 3D frustum based vertex selection.&lt;br /&gt;
#* Spherical selection gives us spacial locality of the vertices being selected without relying on OpenGL framebuffer tests, which improves framework performance and flexibility.&lt;br /&gt;
&lt;br /&gt;
==Constructing the PBVH==&lt;br /&gt;
&lt;br /&gt;
The actual PBVH construction is initiated in DerivedMesh.c in mesh_build_data().&lt;br /&gt;
&lt;br /&gt;
Two things are checked before constructing the PBVH. &lt;br /&gt;
# The object mode.&lt;br /&gt;
# Whether or not ob-&amp;gt;sculpt has been initialized.&lt;br /&gt;
&lt;br /&gt;
After BKE_sculpt_update_mesh_elements is finished, ob-&amp;gt;sculpt will contain a pointer to the PBVH. &lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
//Previous mesh_build_data code...&lt;br /&gt;
if (ob-&amp;gt;mode &amp;amp; (OB_MODE_SCULPT | OB_MODE_WEIGHT_PAINT | OB_MODE_VERTEX_PAINT) &amp;amp;&amp;amp; ob-&amp;gt;sculpt) {&lt;br /&gt;
	/* create PBVH immediately (would be created on the fly too,&lt;br /&gt;
	 * but this avoids waiting on first stroke) */&lt;br /&gt;
&lt;br /&gt;
	BKE_sculpt_update_mesh_elements(scene, scene-&amp;gt;toolsettings-&amp;gt;sculpt, ob, false, false);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Selecting PBVH Nodes==&lt;br /&gt;
After constructing the PBVH tree, we select the nodes containing the vertices we'd like to influence.&lt;br /&gt;
&lt;br /&gt;
We do this in paint_vertex.c in both vpaint_do_paint and wpaint_do_paint by using a pbvh api which takes a boolean callback.&lt;br /&gt;
* In our case, this callback is a sphere-AABB intersection test:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
//Previous vwpaint_do_paint code&lt;br /&gt;
BKE_pbvh_search_gather(ss-&amp;gt;pbvh, sculpt_search_sphere_cb, &amp;amp;data, &amp;amp;nodes, &amp;amp;totnode);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This method gives us the total nodes in our spherical brush region as well as the nodes themselves.&lt;br /&gt;
&lt;br /&gt;
==Selecting Vertices==&lt;br /&gt;
&lt;br /&gt;
After gathering the PBVH nodes to be modified, we paint vertices within them by calling the following method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
//Previous wpaint_do_paint code...&lt;br /&gt;
wpaint_paint_leaves(C, ob, sd, wp, wpd, wpi, me, nodes, totnode);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
//Previous vpaint_do_paint code...&lt;br /&gt;
vpaint_paint_leaves(C, sd, vd, vpd, ob, me, nodes, totnode);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These functions determine which code is associated with the selected tool, and then queues the appropriate tool callback for each PBVH node in parallel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
//See also wpaint_paint_leaves&lt;br /&gt;
static void vpaint_paint_leaves(bContext *C, Sculpt *sd, VPaint *vp, VPaintData *vpd, Object *ob, Mesh *me, &lt;br /&gt;
  PBVHNode **nodes, int totnode)&lt;br /&gt;
{&lt;br /&gt;
	Brush *brush = ob-&amp;gt;sculpt-&amp;gt;cache-&amp;gt;brush;&lt;br /&gt;
&lt;br /&gt;
	SculptThreadedTaskData data = {&lt;br /&gt;
		.sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .vp = vp, .vpd = vpd, &lt;br /&gt;
		.lcol = (unsigned int*)me-&amp;gt;mloopcol, .me = me, .C = C,&lt;br /&gt;
	};&lt;br /&gt;
&lt;br /&gt;
	switch (brush-&amp;gt;vertexpaint_tool) {&lt;br /&gt;
		case PAINT_BLEND_AVERAGE:&lt;br /&gt;
			calculate_average_color(&amp;amp;data, nodes, totnode);&lt;br /&gt;
			BLI_task_parallel_range_ex( 0, totnode, &amp;amp;data, NULL, 0, do_vpaint_brush_draw_task_cb_ex, true, false);&lt;br /&gt;
			break;&lt;br /&gt;
		case PAINT_BLEND_BLUR:&lt;br /&gt;
			BLI_task_parallel_range_ex( 0, totnode, &amp;amp;data, NULL, 0, do_vpaint_brush_blur_task_cb_ex, true, false);&lt;br /&gt;
			break;&lt;br /&gt;
		case PAINT_BLEND_SMUDGE:&lt;br /&gt;
			BLI_task_parallel_range_ex( 0, totnode, &amp;amp;data, NULL, 0, do_vpaint_brush_smudge_task_cb_ex, true, false);&lt;br /&gt;
			break;&lt;br /&gt;
		default:&lt;br /&gt;
			BLI_task_parallel_range_ex( 0, totnode, &amp;amp;data, NULL, 0, do_vpaint_brush_draw_task_cb_ex, true, false);&lt;br /&gt;
			break;&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Each callback is unique. However some tools are grouped together in one callback. &lt;br /&gt;
* Both &amp;quot;mix&amp;quot; and &amp;quot;subtract&amp;quot; blend modes are handled under do_vpaint_brush_draw_task_cb_ex. &lt;br /&gt;
* &amp;quot;blur&amp;quot;, &amp;quot;average&amp;quot;, and &amp;quot;smudge&amp;quot; modes have different control flow and separated into their own callbacks. &lt;br /&gt;
* If a tool doesn't have it's own callback, we default to the do_vpaint_draw_task_cb_ex callback.&lt;br /&gt;
&lt;br /&gt;
Most vertex and weight paint callbacks look like the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
static void do_vpaint_brush_draw_task_cb_ex(&lt;br /&gt;
  void *userdata, void *UNUSED(userdata_chunk), const int n, const int UNUSED(thread_id))&lt;br /&gt;
{&lt;br /&gt;
  /*&lt;br /&gt;
    Fetch required data from &amp;quot;userdata&amp;quot; here...&lt;br /&gt;
  */&lt;br /&gt;
&lt;br /&gt;
  //for each vertex&lt;br /&gt;
  PBVHVertexIter vd;&lt;br /&gt;
  BKE_pbvh_vertex_iter_begin(ss-&amp;gt;pbvh, data-&amp;gt;nodes[n], vd, PBVH_ITER_UNIQUE)&lt;br /&gt;
  {&lt;br /&gt;
    SculptBrushTest test;&lt;br /&gt;
    sculpt_brush_test_init(ss, &amp;amp;test);&lt;br /&gt;
&lt;br /&gt;
    //Test to see if the vertex coordinates are within the spherical brush region.&lt;br /&gt;
    if (sculpt_brush_test(&amp;amp;test, vd.co)) {&lt;br /&gt;
      /*&lt;br /&gt;
        Influence vertex here...&lt;br /&gt;
      */&lt;br /&gt;
    }&lt;br /&gt;
    BKE_pbvh_vertex_iter_end;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Painting Vertices==&lt;br /&gt;
Now that we've covered the PBVH node callbacks, here's an example of one in use with vertex paint.&lt;br /&gt;
* After testing if the vertex is in our brush, we calculate the brush fade using the distance from the brush center to the vertex.&lt;br /&gt;
* If we're using the texture option, we substitute the current color with a texture sampled color.&lt;br /&gt;
* Lastly, we paint every loop that vertex owns.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
  //for each vertex&lt;br /&gt;
  PBVHVertexIter vd;&lt;br /&gt;
  BKE_pbvh_vertex_iter_begin(ss-&amp;gt;pbvh, data-&amp;gt;nodes[n], vd, PBVH_ITER_UNIQUE)&lt;br /&gt;
  {&lt;br /&gt;
    SculptBrushTest test;&lt;br /&gt;
    sculpt_brush_test_init(ss, &amp;amp;test);&lt;br /&gt;
&lt;br /&gt;
    //Test to see if the vertex coordinates are within the spherical brush region.&lt;br /&gt;
    if (sculpt_brush_test(&amp;amp;test, vd.co)) {&lt;br /&gt;
      int vertexIndex = vd.vert_indices[vd.i];&lt;br /&gt;
      const float fade = BKE_brush_curve_strength(brush, test.dist, cache-&amp;gt;radius);&lt;br /&gt;
      unsigned int actualColor = data-&amp;gt;vpd-&amp;gt;paintcol;&lt;br /&gt;
&lt;br /&gt;
      float alpha = 1.0;&lt;br /&gt;
      if (data-&amp;gt;vpd-&amp;gt;is_texbrush) {&lt;br /&gt;
        handle_texture_brush(brush, data, vd, &amp;amp;alpha, &amp;amp;actualColor);&lt;br /&gt;
      }&lt;br /&gt;
      //if a vertex is within the brush region, then paint each loop that vertex owns.&lt;br /&gt;
      for (int j = 0; j &amp;lt; ss-&amp;gt;vert_to_loop[vertexIndex].count; ++j) {&lt;br /&gt;
        int loopIndex = ss-&amp;gt;vert_to_loop[vertexIndex].indices[j];&lt;br /&gt;
        //Mix the new color with the original based on the brush strength and the curve.&lt;br /&gt;
        lcol[loopIndex] = vpaint_blend(data-&amp;gt;vp, lcol[loopIndex], lcolorig[loopIndex], actualColor,         &lt;br /&gt;
                            255 * fade * bstrength * dot * alpha, brush_alpha_pressure * 255);&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    BKE_pbvh_vertex_iter_end;&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The Painting Process Visualized==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;div style=&amp;quot;float: left; width: 50%;&amp;quot;&amp;gt;&lt;br /&gt;
    [[File:N8V-PBVH-Painting-PBVH.png|thumb|none|350px|1. Construct the PBVH.]]&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;div style=&amp;quot;float: left; width: 50%;&amp;quot;&amp;gt;&lt;br /&gt;
    [[File:N8V-PBVH-Painting-Nodes.png|thumb|none|360px|2. Select the nodes touching the brush.]]&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;div style=&amp;quot;float: left; width: 50%;&amp;quot;&amp;gt;&lt;br /&gt;
    [[File:N8V-PBVH-Painting-Sphere-Intersection.png|thumb|none|350px|3. Select vertices within the brush in a callback]]&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;div style=&amp;quot;float: left; width: 50%;&amp;quot;&amp;gt;  &lt;br /&gt;
    [[File:N8V-PBVH-Painting-PBVH-With-Curve.png|thumb|none|350px|4. Use vertex to brush distance for falloff curve]]&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;amp;nbsp;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Splash Prevention==&lt;br /&gt;
A &amp;quot;splash&amp;quot; occurs when a user accidentally paints a vertex. This happens very often with 3D Frustum vertex selection since it's very easy to accidentally catch a vertex in the background when painting vertices in the foreground.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;float: left; width: 40%;&amp;quot;&amp;gt; [[File:N8V-PBVH-Painting-SplashingBefore1.png|thumb|none|200px|Using 3D frustum selection]] &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;float: left; width: 60%;&amp;quot;&amp;gt; [[File:N8V-PBVH-Painting-SplashingBefore2.png|thumb|none|415px|Splashing due to perspective constraints]] &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;amp;nbsp;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Although sphere intersection removes the majority of this problem, we still have some splash situations.&lt;br /&gt;
* A sphere might catch hidden vertices that still lie within the brush region.&lt;br /&gt;
* A sphere might catch visible vertices across the 3D cursor location, like on the edge of a saddle.&lt;br /&gt;
 &lt;br /&gt;
To mitigate this problem, we compare vertex normals with the surface normal at the 3D cursor location by using a dot product.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
const float dot = dot_vf3vs3(cache-&amp;gt;sculpt_normal_symm, vd.no);&lt;br /&gt;
//If both surface normals are in the same direction&lt;br /&gt;
if (dot &amp;gt; 0.0)&lt;br /&gt;
        for (int j = 0; j &amp;lt; ss-&amp;gt;vert_to_loop[vertexIndex].count; ++j) {&lt;br /&gt;
          int loopIndex = ss-&amp;gt;vert_to_loop[vertexIndex].indices[j];&lt;br /&gt;
&lt;br /&gt;
          //Mix the new color with the original based on the brush strength, &lt;br /&gt;
           //the curve, the alpha, and the dot product of the surface normals.&lt;br /&gt;
          lcol[loopIndex] = vpaint_blend(data-&amp;gt;vp, lcol[loopIndex], lcolorig[loopIndex], &lt;br /&gt;
                                         actualColor, 255 * fade * bstrength * dot * alpha, &lt;br /&gt;
                                         brush_alpha_pressure * 255);&lt;br /&gt;
        }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;float: left; width: 50%;&amp;quot;&amp;gt; [[File:N8V-PBVH-Painting-Without-Splash-Prevention.png|thumb|none|365px|Before]] &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;float: left; width: 50%;&amp;quot;&amp;gt; [[File:N8V-PBVH-Painting-With-Splash-Prevention.png|thumb|none|400px|After]] &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;amp;nbsp;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|Note|Splash prevention may become more sophisticated in the future. }}&lt;br /&gt;
&lt;br /&gt;
==Mirroring and Radial Symmetry==&lt;br /&gt;
&lt;br /&gt;
In addition to the initial stroke, our wpaint_do_paint and vpaint_do_paint methods are both called for each radial symmetry pass.&lt;br /&gt;
* This is otherwise known as 'axial symmetry'&lt;br /&gt;
* This code is very similar to sculpt mode!&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
static void vpaint_do_radial_symmetry(bContext *C, Sculpt *sd, VPaint *vd, VPaintData *vpd, Object *ob, &lt;br /&gt;
  Mesh *me, Brush *brush, const char symm, const int axis)&lt;br /&gt;
{&lt;br /&gt;
	for (int i = 1; i &amp;lt; vd-&amp;gt;radial_symm[axis - 'X']; ++i) {&lt;br /&gt;
		const float angle = (2.0 * M_PI) * i / vd-&amp;gt;radial_symm[axis - 'X'];&lt;br /&gt;
		vpaint_do_paint(C, sd, vd, vpd, ob, me, brush, symm, axis, i, angle);&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
static void wpaint_do_radial_symmetry(bContext *C, Object *ob, VPaint *wp, Sculpt *sd, WPaintData *wpd, WeightPaintInfo *wpi, &lt;br /&gt;
  Mesh *me, Brush *brush, const char symm, const int axis)&lt;br /&gt;
{&lt;br /&gt;
	for (int i = 1; i &amp;lt; wp-&amp;gt;radial_symm[axis - 'X']; ++i) {&lt;br /&gt;
		const float angle = (2.0 * M_PI) * i / wp-&amp;gt;radial_symm[axis - 'X'];&lt;br /&gt;
		wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, symm, axis, i, angle);&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Lastly, we add the ability to mirror the initial and radial painting across the X, Y, and/or Z axis.&lt;br /&gt;
* This is otherwise known as &amp;quot;bilateral symmetry'&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
//See also wpaint_do_symmetrical_brush_actions&lt;br /&gt;
static void vpaint_do_symmetrical_brush_actions(bContext *C, Sculpt *sd, VPaint *vd, VPaintData *vpd, Object *ob)&lt;br /&gt;
{&lt;br /&gt;
	Brush *brush = BKE_paint_brush(&amp;amp;vd-&amp;gt;paint);&lt;br /&gt;
	Mesh *me = ob-&amp;gt;data;&lt;br /&gt;
	SculptSession *ss = ob-&amp;gt;sculpt;&lt;br /&gt;
	StrokeCache *cache = ss-&amp;gt;cache;&lt;br /&gt;
	const char symm = vd-&amp;gt;paint.symmetry_flags &amp;amp; PAINT_SYMM_AXIS_ALL;&lt;br /&gt;
	int i = 0;&lt;br /&gt;
&lt;br /&gt;
	/* initial stroke */&lt;br /&gt;
	vpaint_do_paint(C, sd, vd, vpd, ob, me, brush, i, 'X', 0, 0);&lt;br /&gt;
	vpaint_do_radial_symmetry(C, sd, vd, vpd, ob, me, brush, i, 'X');&lt;br /&gt;
	vpaint_do_radial_symmetry(C, sd, vd, vpd, ob, me, brush, i, 'Y');&lt;br /&gt;
	vpaint_do_radial_symmetry(C, sd, vd, vpd, ob, me, brush, i, 'Z');&lt;br /&gt;
&lt;br /&gt;
	cache-&amp;gt;symmetry = symm;&lt;br /&gt;
&lt;br /&gt;
	/* symm is a bit combination of XYZ - 1 is mirror X; 2 is Y; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */&lt;br /&gt;
	for (i = 1; i &amp;lt;= symm; ++i) {&lt;br /&gt;
		if (symm &amp;amp; i &amp;amp;&amp;amp; (symm != 5 || i != 3) &amp;amp;&amp;amp; (symm != 6 || (i != 3 &amp;amp;&amp;amp; i != 5))) {&lt;br /&gt;
			cache-&amp;gt;mirror_symmetry_pass = i;&lt;br /&gt;
			cache-&amp;gt;radial_symmetry_pass = 0;&lt;br /&gt;
			calc_brushdata_symm(vd, cache, i, 0, 0);&lt;br /&gt;
&lt;br /&gt;
			if (i &amp;amp; 1 &amp;lt;&amp;lt; 0) {&lt;br /&gt;
				vpaint_do_paint(C, sd, vd, vpd, ob, me, brush, i, 'X', 0, 0);&lt;br /&gt;
				vpaint_do_radial_symmetry(C, sd, vd, vpd, ob, me, brush, i, 'X');&lt;br /&gt;
			}&lt;br /&gt;
			if (i &amp;amp; 1 &amp;lt;&amp;lt; 1) {&lt;br /&gt;
				vpaint_do_paint(C, sd, vd, vpd, ob, me, brush, i, 'Y', 0, 0);&lt;br /&gt;
				vpaint_do_radial_symmetry(C, sd, vd, vpd, ob, me, brush, i, 'Y');&lt;br /&gt;
			}&lt;br /&gt;
			if (i &amp;amp; 1 &amp;lt;&amp;lt; 2) {&lt;br /&gt;
				vpaint_do_paint(C, sd, vd, vpd, ob, me, brush, i, 'Z', 0, 0);&lt;br /&gt;
				vpaint_do_radial_symmetry(C, sd, vd, vpd, ob, me, brush, i, 'Z');&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	copy_v3_v3(cache-&amp;gt;true_last_location, cache-&amp;gt;true_location);&lt;br /&gt;
	cache-&amp;gt;is_last_valid = true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These functions are called in the update_step portion of the vertex painting operator.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
//Previous vpaint_stroke_update_step code...&lt;br /&gt;
vpaint_do_symmetrical_brush_actions(C, sd, vp, vpd, ob);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
//Previous wpaint_stroke_update_step code...&lt;br /&gt;
wpaint_do_symmetrical_brush_actions(C, ob, wp, sd, wpd, &amp;amp;wpi);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These are the results.&lt;br /&gt;
&amp;lt;div align=&amp;quot;center&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;div style=&amp;quot;float: left; width: 50%;&amp;quot;&amp;gt;&lt;br /&gt;
    [[File:N8V-PBVH-Painting-Radial1.png|thumb|none|200px|Radial Settings]]&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;div style=&amp;quot;float: left; width: 50%;&amp;quot;&amp;gt;&lt;br /&gt;
    [[File:N8V-PBVH-Painting-Radial2.png|thumb|none|200px|Radial Symmetry]]&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;div style=&amp;quot;float: left; width: 50%;&amp;quot;&amp;gt;&lt;br /&gt;
    [[File:N8V-PBVH-Painting-Bilateral1.png|thumb|none|200px|Bilateral Settings]]&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;div style=&amp;quot;float: left; width: 50%;&amp;quot;&amp;gt;  &lt;br /&gt;
    [[File:N8V-PBVH-Painting-Bilateral2.png|thumb|none|200px|Bilateral Symmetry]]&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;amp;nbsp;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Adding Brushes and Blend Modes==&lt;br /&gt;
There are a couple steps that need to be done before a new brush will appear in blender's weight painting and vertex painting modes.&lt;br /&gt;
&lt;br /&gt;
You'll need to add an enumerator in DNA_brush_types.h. This is what the current enumeration looks like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
enum {&lt;br /&gt;
	PAINT_BLEND_MIX = 0,&lt;br /&gt;
	PAINT_BLEND_ADD = 1,&lt;br /&gt;
	PAINT_BLEND_SUB = 2,&lt;br /&gt;
	PAINT_BLEND_MUL = 3,&lt;br /&gt;
	PAINT_BLEND_BLUR = 4,&lt;br /&gt;
	PAINT_BLEND_LIGHTEN = 5,&lt;br /&gt;
	PAINT_BLEND_DARKEN = 6,&lt;br /&gt;
	PAINT_BLEND_AVERAGE = 7,&lt;br /&gt;
	PAINT_BLEND_SMUDGE = 8&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then, you'll need to account for the new blend mode in vpaint_blend_tool and/or wpaint_blend_tool in paint_vertex.c.&lt;br /&gt;
* If your new brush only changes how each vertex reacts to the brush color, you'll only need to write an mcol_blend routine.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
/* wpaint has 'wpaint_blend_tool' */&lt;br /&gt;
static unsigned int vpaint_blend_tool(const int tool, const unsigned int col,&lt;br /&gt;
                                      const unsigned int paintcol, const int alpha_i)&lt;br /&gt;
{&lt;br /&gt;
	switch (tool) {&lt;br /&gt;
		case PAINT_BLEND_MIX:&lt;br /&gt;
		case PAINT_BLEND_BLUR:     return mcol_blend(col, paintcol, alpha_i);&lt;br /&gt;
		case PAINT_BLEND_AVERAGE:  return mcol_blend(col, paintcol, alpha_i);&lt;br /&gt;
		case PAINT_BLEND_SMUDGE:   return mcol_blend(col, paintcol, alpha_i);&lt;br /&gt;
		case PAINT_BLEND_ADD:      return mcol_add(col, paintcol, alpha_i);&lt;br /&gt;
		case PAINT_BLEND_SUB:      return mcol_sub(col, paintcol, alpha_i);&lt;br /&gt;
		case PAINT_BLEND_MUL:      return mcol_mul(col, paintcol, alpha_i);&lt;br /&gt;
		case PAINT_BLEND_LIGHTEN:  return mcol_lighten(col, paintcol, alpha_i);&lt;br /&gt;
		case PAINT_BLEND_DARKEN:   return mcol_darken(col, paintcol, alpha_i);&lt;br /&gt;
		default:&lt;br /&gt;
			BLI_assert(0);&lt;br /&gt;
			return 0;&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you need more flexibility with your brush, you'll need to create and add your own callback to be used in vpaint_paint_leaves and/or wpaint_paint_leaves.&lt;br /&gt;
* I do this for the smudge brush and average brush discussed below.&lt;br /&gt;
&lt;br /&gt;
You'll need to add an entry to rna_enum_brush_vertex_tool_items in rna_brush.c.&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
EnumPropertyItem rna_enum_brush_vertex_tool_items[] = {&lt;br /&gt;
	{PAINT_BLEND_MIX, &amp;quot;MIX&amp;quot;, ICON_BRUSH_MIX, &amp;quot;Mix&amp;quot;, &amp;quot;Use mix blending mode while painting&amp;quot;},&lt;br /&gt;
	{PAINT_BLEND_ADD, &amp;quot;ADD&amp;quot;, ICON_BRUSH_ADD, &amp;quot;Add&amp;quot;, &amp;quot;Use add blending mode while painting&amp;quot;},&lt;br /&gt;
	{PAINT_BLEND_SUB, &amp;quot;SUB&amp;quot;, ICON_BRUSH_SUBTRACT, &amp;quot;Subtract&amp;quot;, &amp;quot;Use subtract blending mode while painting&amp;quot;},&lt;br /&gt;
	{PAINT_BLEND_MUL, &amp;quot;MUL&amp;quot;, ICON_BRUSH_MULTIPLY, &amp;quot;Multiply&amp;quot;, &amp;quot;Use multiply blending mode while painting&amp;quot;},&lt;br /&gt;
	{PAINT_BLEND_BLUR, &amp;quot;BLUR&amp;quot;, ICON_BRUSH_BLUR, &amp;quot;Blur&amp;quot;, &amp;quot;Blur the color with surrounding values&amp;quot;},&lt;br /&gt;
	{PAINT_BLEND_LIGHTEN, &amp;quot;LIGHTEN&amp;quot;, ICON_BRUSH_LIGHTEN, &amp;quot;Lighten&amp;quot;, &amp;quot;Use lighten blending mode while painting&amp;quot;},&lt;br /&gt;
	{PAINT_BLEND_DARKEN, &amp;quot;DARKEN&amp;quot;, ICON_BRUSH_DARKEN, &amp;quot;Darken&amp;quot;, &amp;quot;Use darken blending mode while painting&amp;quot;},&lt;br /&gt;
	{PAINT_BLEND_AVERAGE, &amp;quot;AVERAGE&amp;quot;, ICON_BRUSH_BLUR, &amp;quot;Average&amp;quot;, &amp;quot;Use average blending mode while painting&amp;quot; },&lt;br /&gt;
	{PAINT_BLEND_SMUDGE, &amp;quot;SMUDGE&amp;quot;, ICON_BRUSH_BLUR, &amp;quot;Smudge&amp;quot;, &amp;quot;Use smudge blending mode while painting&amp;quot; },&lt;br /&gt;
	{0, NULL, 0, NULL, NULL}&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Lastly, you'll need to add your brush to the default brushes by modifying versioning_defaults.c&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void BLO_update_defaults_startup_blend(Main *bmain) &lt;br /&gt;
{&lt;br /&gt;
  /*&lt;br /&gt;
    Previous startup_blend code...&lt;br /&gt;
  */&lt;br /&gt;
&lt;br /&gt;
  //Add the new brush as a default option if not previously added...&lt;br /&gt;
  br = (Brush *)BKE_libblock_find_name_ex(bmain, ID_BR, &amp;quot;Smudge&amp;quot;);&lt;br /&gt;
  if (!br) {&lt;br /&gt;
    br = BKE_brush_add(bmain, &amp;quot;Smudge&amp;quot;, OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT);&lt;br /&gt;
    br-&amp;gt;vertexpaint_tool = PAINT_BLEND_SMUDGE;&lt;br /&gt;
    br-&amp;gt;ob_mode = OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==More complicated brushes==&lt;br /&gt;
I mentioned earlier that occasionally we'll need a more flexible way to add brushes than simply creating an mcol_blend routine.&lt;br /&gt;
* We can get that additional flexibility by adding callbacks.&lt;br /&gt;
* Smudge mode is a good example, since vertices require information about their neighbors before being painted.&lt;br /&gt;
&lt;br /&gt;
All the code for smudge is in the same 'smudge' callback below. After writing this callback, I use this callback in vpaint_paint_leaves&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
static void do_vpaint_brush_smudge_task_cb_ex(&lt;br /&gt;
  void *userdata, void *UNUSED(userdata_chunk), const int n, const int UNUSED(thread_id))&lt;br /&gt;
{&lt;br /&gt;
	SculptThreadedTaskData *data = userdata;&lt;br /&gt;
	SculptSession *ss = data-&amp;gt;ob-&amp;gt;sculpt;&lt;br /&gt;
	Brush *brush = data-&amp;gt;brush;&lt;br /&gt;
	StrokeCache *cache = ss-&amp;gt;cache;&lt;br /&gt;
	const float bstrength = cache-&amp;gt;bstrength;&lt;br /&gt;
	bool shouldColor = false;&lt;br /&gt;
	unsigned int *lcol = data-&amp;gt;lcol;&lt;br /&gt;
	unsigned int *lcolorig = data-&amp;gt;vp-&amp;gt;vpaint_prev;&lt;br /&gt;
	unsigned int finalColor;&lt;br /&gt;
	float brushDirection[3];&lt;br /&gt;
	sub_v3_v3v3(brushDirection, cache-&amp;gt;location, cache-&amp;gt;last_location);&lt;br /&gt;
	normalize_v3(brushDirection);&lt;br /&gt;
&lt;br /&gt;
	//If the position from the last update is initialized...&lt;br /&gt;
          //This is needed to calculate a brush direction&lt;br /&gt;
	if (cache-&amp;gt;is_last_valid) {&lt;br /&gt;
		//for each vertex&lt;br /&gt;
		PBVHVertexIter vd;&lt;br /&gt;
		BKE_pbvh_vertex_iter_begin(ss-&amp;gt;pbvh, data-&amp;gt;nodes[n], vd, PBVH_ITER_UNIQUE)&lt;br /&gt;
		{&lt;br /&gt;
			SculptBrushTest test;&lt;br /&gt;
			sculpt_brush_test_init(ss, &amp;amp;test);&lt;br /&gt;
&lt;br /&gt;
			//Test to see if the vertex coordinates are within the spherical brush region.&lt;br /&gt;
			if (sculpt_brush_test(&amp;amp;test, vd.co)) {&lt;br /&gt;
                                //Verify we aren't splashing...&lt;br /&gt;
				float dot = dot_vf3vs3(cache-&amp;gt;sculpt_normal_symm, vd.no);&lt;br /&gt;
				if (dot &amp;gt; 0.0) {&lt;br /&gt;
					const float fade = BKE_brush_curve_strength(brush, test.dist, cache-&amp;gt;radius);&lt;br /&gt;
					int vertexIndex = vd.vert_indices[vd.i];&lt;br /&gt;
					MVert *currentVert = &amp;amp;data-&amp;gt;me-&amp;gt;mvert[vertexIndex];&lt;br /&gt;
&lt;br /&gt;
					//Minimum dot product between brush direction and current to neighbor direction is 0.0,  &lt;br /&gt;
                                         //meaning orthogonal.&lt;br /&gt;
					float maxDotProduct = 0.0f;&lt;br /&gt;
&lt;br /&gt;
					//Get the color of the loop in the opposite direction of the brush movement&lt;br /&gt;
					finalColor = 0;&lt;br /&gt;
					for (int j = 0; j &amp;lt; ss-&amp;gt;vert_to_poly[vertexIndex].count; j++) {&lt;br /&gt;
						int polyIndex = ss-&amp;gt;vert_to_poly[vertexIndex].indices[j];&lt;br /&gt;
						MPoly *poly = &amp;amp;data-&amp;gt;me-&amp;gt;mpoly[polyIndex];&lt;br /&gt;
						for (int k = 0; k &amp;lt; poly-&amp;gt;totloop; k++) {&lt;br /&gt;
							unsigned int loopIndex = poly-&amp;gt;loopstart + k;&lt;br /&gt;
							MLoop *loop = &amp;amp;data-&amp;gt;me-&amp;gt;mloop[loopIndex];&lt;br /&gt;
							unsigned int neighborIndex = loop-&amp;gt;v;&lt;br /&gt;
							MVert *neighbor = &amp;amp;data-&amp;gt;me-&amp;gt;mvert[neighborIndex];&lt;br /&gt;
&lt;br /&gt;
							//Get the direction from the selected vert to the neighbor.&lt;br /&gt;
							float toNeighbor[3];&lt;br /&gt;
							sub_v3_v3v3(toNeighbor, currentVert-&amp;gt;co, neighbor-&amp;gt;co);&lt;br /&gt;
							normalize_v3(toNeighbor);&lt;br /&gt;
&lt;br /&gt;
							float dotProduct = dot_v3v3(toNeighbor, brushDirection);&lt;br /&gt;
&lt;br /&gt;
							if (dotProduct &amp;gt; maxDotProduct) {&lt;br /&gt;
								maxDotProduct = dotProduct;&lt;br /&gt;
								finalColor = lcol[loopIndex];&lt;br /&gt;
								shouldColor = true;&lt;br /&gt;
							}&lt;br /&gt;
						}&lt;br /&gt;
					}&lt;br /&gt;
                                        //If we found a neighbor behind the brush direction...&lt;br /&gt;
					if (shouldColor) {&lt;br /&gt;
						//then paint each loop that vertex owns.&lt;br /&gt;
						for (int j = 0; j &amp;lt; ss-&amp;gt;vert_to_loop[vertexIndex].count; ++j) {&lt;br /&gt;
							int loopIndex = ss-&amp;gt;vert_to_loop[vertexIndex].indices[j];&lt;br /&gt;
							//Mix the new color with the original based on some parameters.&lt;br /&gt;
							lcol[loopIndex] = vpaint_blend(data-&amp;gt;vp, lcol[loopIndex], lcolorig[loopIndex],&lt;br /&gt;
                                                                                   finalColor, 255.0 * fade * bstrength * dot, 255.0);&lt;br /&gt;
						}&lt;br /&gt;
					}&lt;br /&gt;
				}&lt;br /&gt;
			}&lt;br /&gt;
			BKE_pbvh_vertex_iter_end;&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Email / IRC / Social / Web==&lt;br /&gt;
If I missed out on an important detail, or if this documentation doesn't answer your question, feel free to shoot me a message over &lt;br /&gt;
email, IRC, or on twitter.&lt;br /&gt;
&lt;br /&gt;
bitinat2@isu.edu or nathanvollmer@outlook.com&lt;br /&gt;
&lt;br /&gt;
N8V or nathanvollmer on #blendercoders&lt;br /&gt;
&lt;br /&gt;
Twitter: https://twitter.com/NathanVollmer&lt;br /&gt;
&lt;br /&gt;
Web: http://www2.cose.isu.edu/~bitinat2&lt;/div&gt;</summary>
		<author><name>wiki&gt;Bitinat2</name></author>
		
	</entry>
</feed>