This page includes documentation for GSOC 2012 on texture painting tools:
The proposed tools are:
Implemented during GSOC are:
Rake brushes for texture paint
Port the sculpting brush functionality to texture painting
- User rotation through slider and Ctrl-F
- Random brush rotation
- Rake style painting
To use: select through drop-down menu and tweak brush rotation either through Ctrl-F or the angle field
implementation details Implementing rake brushes was not too hard, it has only been a matter of locating the place in the code where the brush coordinate sampled for the texture is resolved. This has been some source of confusion since for 2D painting and projective paintinng the coordinates are computed quite differently. I have given some thought to better separate the two modes. Currently 2D and projection painting share some code that results in some confusion as to what coordinates get sampled. In my opinion, the two modes should be completely separate due to another issue: The 3D mapping of the brush is not quite the same as the 2D mapping, in fact one does not apply to the other. This means that a shared brush could have problems exposing rake on 2D painting (since it is actually dependent on a certain 3D mapping mode that does not have a meaning on 2D painting). The way I have solved this is to temporarily introduce an extraneous mapping mode for texture painting that is valid for rake brushes. This mode is set in the brush display function when the brush is over the 3D view, which is a clear violation of model/view/controller principles.
TODOs: Separate 3d/2d mappings or introduce a separate 2D paint system in Toolsettings, rotation for fixed style brushes.
Random size for strokes (only supported for texture painting currently)
To use: increase the randomize radius slider below the radius slider
implementation details Since the size of the brush is sampled often, I needed an explicit way to randomize a brush radius during the stroke's operation. So I have added a function to set a random brush radious and a function to explicitly get the randomized brush result computed through randomization. This operation may be risky for texture painting because bucket sizes are computed from brush radius. It seems like we allocate buckets up to twice the brush radius though, so randomizing between 0.5-1.5 times the brush radius is safe.
Scale brush size on zooming
To use: click the lock icon next to the radius slider
This feature works best on orthographic mode currently. Making it work on projective mode requires ray casting to find intersection with brush and it won't work correcty for objects that have parts close to the camera and parts far from camera anyway. So I will leave this for ortho mode probably.
implementation details This functionality is hooked into the zoom operator. It would probably be more accurate (as accurate as something like this can be in projective camera mode) to implement this sort of like sculpt mode does it, which is raycasting between mouse position and mesh. However we don't currently maintain a BVH for texture painting (which is something I am contemplating on)
Separate alpha masks for texture paint
Set an extra image to be used as a mask for the texture painting operation.
To use: Tick 'Texture Mask' in the panel header and set an image texture as texture mask
implementation details Most of the work here went to functions that maintain the mask on save/load and scene linking. This functionality works by modifying the alpha result of the texture paint operation. People have also requested that separate mapping is allowed between mask and actual paint texture but currently it only works as a non-tiled view mapped mask, attached to the paint cursor. Implementing extra mappings is actually not hard, and the only difficulty comes from integrating this with texture paint overlay
TODO: Add extra mappings for the masks, Control rotation of masks with hotkey.
Overlay for texture paint/tiled mode brushes
To use: Toggle the overlay button and tweak the overlay alpha
implementation details Doing overlay for texture painting was a matter of using the same brush display function as the one used for sculpting. Unfortunately, there's quite a lot of functionality in sculpt mode that is not exposed in texture painting. So after exposing the python inteface and hooking the display function, I did a lot of cleanup in the code to expose alpha masks, correct display of tiled mode brush overlay and setting of appropriate modes for texture painting (see rake brushes above). The code utilizes the old multitexture feature of opengl and separates textures between 'alpha' or 'curve', which samples the brush carve and mask and actual image texture used for the brush. The two are blended using texture combiners and in order to trim bad alpha values outside the brush radius, resulting in opaque parts, the overlay shape is modified from quad to filled circle to match the brush shape.
TODO: Add support for different mapping modes for alpha masks. Also separate irrelevant code from display functions.
To use: Press ctrl while painting or tick "invert colour" below the colour wheel. Doing both does negates the negation, aka does nothing.
implementation details A straightforward tool, I simply inserted a brush option and an operator option and inverted the final colour if one of the two as set.
Correct UVs tool
Uvs of a mesh change position as you move mesh geometry to keep the image position as constant as possible on the mesh surface.
To use: Enter edit mode and click on Correct UVs option in the toolbar. When the tool is turned on and vertices are being transformed, the tool tries to readjust the uvs so that the image appears to stay in place on the surface.
implementation details This code operates diffently from current correct uv functionality. Instead of copying the whole initial faces adjacent to the vertices being moved and then projecting the vertex back on them, it copies the initial adjacent vertex and uv data and then projects the vertex to the plane defined by the initial vertices closest to the translation of the moving vertex. The difference is that we are not bound by the extent of the adjacent faces anymore and the downside is that we sacrifice some precision. There is a problem when the adjacent vertices form a parallel edge though because then a plane is impossible to form. This is a source of bugs that will have to be addressed. The tool is also better in that it tries to extrapolate the customdata when moving outside the mesh boundaries too (for instance moving a boundary edge on an open mesh). The calculation part works by solving a 2x2 linear system that projects data from 3D space to data space (uv space in our case).
TODO: fix a few bugs, integrate better with existing functionality that corrects all customdata
Take mirror modifier outpur into account when unwrapping. This makes the final unwrap more correct on mirror borders. The user currently has to reposition the uv islands but as a todo, the tool will automatically align them to take the 'mirror uvs' option of the modifier into account, using only half the uv space.
To use: unwrap a mesh with a mirror modifier being first in the stack and tick "use mirror" from operator properties.
implementation details Similarly to how the subsurf unwrapping operates, the unwrapper is tweaked to calculate the output of the mirror modifier and only dispatch customdata uv pointers to the unwrap calculation if they belong to the non mirrored mesh. This way, the calculation uses the full mirrored mesh, but only the non mirrored parts get the result back. This works well but it is a bit intrusive in the unwrap code and it is also a bit expensive to estimate which faces are mirrored and which are not. I have experimented with a CustomData layer during development and I may reuse it when I clean up the code.
TODO: automatically align the uv islands, to taking the 'mirror uvs' option of the modifier into account, using only half the uv space. Clenaup.
isomap based unwrapping: unwrap solver based on the isomap algorithm
New unwrapping algorithm, supposedly producing better results than ABF++. This is WIP currently
To use: unwrap with U in edit mode and select ISOMAP from operator properties
implementation details The algorithm of isomap relies on performing MDS (Multi Data Scaling) on a matrix D containing the geodesic mesh distances between vertices i,j as its D(i,j) elements. MDS relies on eigenvalue/eigenvector decomposition so I relied on the Eigen3 library to make this work. Also, I found with experimentation that the quality of the unwrapping is greatly influenced by the quality of the geodesic distance estimation. To efficiently (in terms of computational complexity) calculate the geodesic distance between all points, I use a djikstra algorithm, where vertices are the nodes of the graph and node connectivity is inferred by the mesh edges. However, this only gives a good way to traverse the mesh, not a way to accurately compute the geodesic distance over the mesh. I have found a good algorithm that uses a quadratic equation for the estimation of distance when traversing the graph, by using two points in the djiksra graph instead of one and inscribing a circle around the two points in graph space, see (4). Picking between the two solutions of the equation is based on a criterion that does not always hold unfortunately, so another criterion is needed. However the results look quite promising. Another theoretical problem that will need to be tackled is support for pinned UVs. Due to the nature of the solving algorithm, I haven't found a way to constrain the solution. Maybe some paper yet unread holds the solution.
References/papers used in the code:
- 1) Texture Mapping using Surface Flattening via Multi-Dimensional Scaling, Gil Zigelman, Ron Kimmel and Nahum Kiryati
- 2) A Global Geometric Framework for Nonlinear Dimensionality Reduction, Joshua B. Tenenbaum, Vin de Silva, John C. Langford
- 3) Computing Geodesic Paths on Manifolds, R Kimmel J.A. Sethian
- 4) Computing Geodesic Distances on Triangular Meshes, Marcin Novotni and Reinhard Klein