1
1

Compare commits

...

186 Commits

Author SHA1 Message Date
fb7c003e99 Simple CSV mesh loading for tests.
File paths are crude and preliminary.
2017-08-28 08:21:57 +01:00
caded470a6 Fix neighbor conflict checks for Poisson disk distribution.
The cell size is chosen as r/sqrt(3) (i.e. max. 1 sample per cell),
which means that the influence of each cell extends beyond the 1st
neighbor. a 5x5x5 box (except the center and corners) needs to be
examined to find collisions.
2017-08-26 15:32:09 +01:00
d9a4945bb2 Added a 1-cell margin to the grid dimensions to simplify neighbor lookups. 2017-08-26 12:41:58 +01:00
188874af2f Separate step for mesh sample generators to bind them to a specific mesh. 2017-08-26 12:13:52 +01:00
e3033693f9 Initial implementation of Poisson disk sampling on meshes.
This is still buggy (uneven sample coverage) and needs changes to the
sampling system for better threading support.

The implementation is based on

Bowers, John, et al. "Parallel Poisson disk sampling with spectrum analysis on surfaces." ACM Transactions on Graphics (TOG). Vol. 29. No. 6. ACM, 2010.
2017-08-25 09:19:49 +01:00
ef67e13bdb Thread-safe implementation of the volume bounding-box raycast sampler. 2017-08-23 09:44:00 +01:00
9e8a41aadf Include other sample generators in tests.
Volume bbray method is currently excluded because the implementation
is not yet thread-safe.
2017-08-23 09:07:45 +01:00
8b607d77e5 New gtests for the mesh sampling system.
This should test general functionality and consistency of different methods
for generating samples. In particular it shoud ensure that threaded variants
produce the same samples as unthreaded variants when using the same generator
and seeds.
2017-08-22 18:49:01 +01:00
0d4fa23414 Use a consistent number of RNG steps in both sample generation and skipping.
This is to ensure that threaded generator usage creates the exact same results
as the non-threaded base version. Further improvements and testing will be
needed.
2017-08-21 06:42:59 +01:00
e3b80d6202 Multithreading support for the mesh sampling library.
Samples can be generated either one-by-one using the existing "make_sample"
function (which uses a default context), or use the more efficient batch
generation functions. These optionally support threading via the task scheduler
now.
2017-08-20 18:50:58 +01:00
ead00baab1 Fix hair follicle array write/read. 2017-08-19 12:26:02 +01:00
45f0f3dc04 Merge branch 'blender2.8' into strand_editmode 2017-08-19 12:02:03 +01:00
0d67f8d5c4 Removed unused shaders in the draw modes module. 2017-08-19 08:56:04 +01:00
438b3f95a1 Implement control strands generation for basic hair types along surface normals.
The default hair group type uses vertex normals to generate guide strands automatically.
2017-08-18 16:13:34 +01:00
1b837226ce Store the scalp mesh as part of the hair drawing interface.
The scalp mesh is always needed for evaluating root positions anyway.
2017-08-14 08:46:10 +01:00
6faa4d77c1 Removed fiber drawing from the strand edit mode to simplify code.
For rendering hair fibers the edit mode should just use the underlying data.
2017-08-14 08:19:54 +01:00
b0717ad91e Hair drawing data based on the new DNA hair groups. 2017-08-14 07:58:02 +01:00
91fd0a487d Store pointer into the hair follicle array in hair groups directly.
This creates a dependency of the hair groups on the validity of the pattern,
so care has to be taken to always update the groups when the pattern changes.
2017-08-11 08:57:04 +01:00
e7df5f8528 Split off hair drawing code into a separate file. 2017-08-11 08:17:03 +01:00
6fa838f6d3 List of 'Hair Groups' in hair patterns that will generate actual fibers for rendering.
Each group affects a subset of the hair follicles, so that multiple effects can be combined
without overlapping sample sets.
2017-08-07 18:04:30 +01:00
41e8bd9337 Merge branch 'blender2.8' of git.blender.org:blender into strand_editmode 2017-08-07 13:06:02 +01:00
77802b21a6 Operator for generating a hair follicle distribution. 2017-08-07 12:29:12 +01:00
8abe6745a4 DNA data + modifier for storing persistent hair data.
This stores basic hair follicle data, rather than full fiber caches
(which should only be generated for render data).

Note that deformation by strands will just be one possible way to generate
hair fibers, so strands should be kept separate from the hair follicle data somewhat.
2017-08-07 10:54:02 +01:00
53eb011e3d Prototype hair deformers for generated detail like clumping and curling.
Currently only clumping and curls are implemented. There is no proper input
for the parameters yet, these are just hardcoded atm. Eventually the hair system
should either define uniform values from hair system settings or enable per-fiber
values using a material or other node-based input.
2017-08-06 12:41:16 +01:00
801c20cebc Some optional timing for strand data buffer construction. 2017-08-04 08:41:20 +01:00
9feec51214 Subdivision of hair fibers for smoother shading.
Subdivision works on the parent strands for efficiency. The fibers lengths
are based on the final subdivided length of parents, so no changes to the
shader are required.

This would be nicer with a tesselation shader, but this feature is not
available in Blender 2.8.
2017-08-03 20:27:33 +01:00
e34ba9fb7a Use a 2D texture for the hair interpolation data instead of 1D for larger number of hairs.
It turns out that 1D textures have the same size limit on their 1 axis as 2D textures.
This limits the potential number of hair dramatically, even though the actual size of
the texture is very small. Using a 2D texture and wrapping the index avoids this problem.
2017-08-03 08:11:48 +01:00
c92457a87a Default settings for hair edit mode tool settings. 2017-08-02 22:35:57 +01:00
bf0f058954 Disable selection outline drawing on the object during hair edit mode. 2017-08-02 22:28:25 +01:00
e35f24fb28 Removed leftover hair fiber code from edit mode drawing (all in Eevee now). 2017-08-02 21:54:01 +01:00
e8ff620325 Moved the hair fiber shader from strand edit mode into Eevee.
This allows the shader to use proper lighting from the standard Eevee shaders.

The code for interpolating hair strands is in a glsl library file to facilitate
use in other engines later.
2017-08-02 21:40:52 +01:00
3a438c675f Hair fiber (child hair) drawing during edit mode, with heavy GPU leveraging.
Hair drawing requires fairly large vertex buffers. Vertices also need to be interpolated
and displaced according to the control (parent) hairs and hair-style features like curling
and clumping.

Instead of doing this in advance on the CPU and uploading a large vertex buffer every frame,
this work can be done on the GPU for realtime display, because interpolation and displacement
are highly parallelizable. The vertex buffers then only need to be uploaded once (as long
as strand lengths remain the same). The vertex format can also be very lightweight, requiring
only a curve parameter and index (half the size needed for full positions).

For interpolating a buffer texture is used in the shader, which contains all information
about the control curves (parents) from which to interpolate final vertex positions.
2017-08-01 08:10:05 +01:00
57cbaa15b6 Custom shaders for drawing strand selection. 2017-07-23 08:32:29 +01:00
679113fbd5 Basic drawing code for hair edit mode in the new 2.8 draw engine system. 2017-07-22 12:35:40 +01:00
65d2374c80 Merge branch 'blender2.8' into strand_editmode2.8 2017-07-16 11:48:39 +01:00
d3349e5e3f Merge branch 'master' into strand_editmode 2016-08-09 15:52:02 +02:00
5a7efaf287 Added "show_brush" option for hair edit settings, expected by common paint UI.
This setting has no effect yet for hair editing, but the UI script expects it.
2016-07-27 09:50:45 +02:00
f3bc942370 Merge branch 'master' into strand_editmode 2016-07-25 09:33:48 +02:00
07cffae7ea Fix for uninitialized v_mirr variable when using edge strokes with mirroring. 2016-07-20 09:44:39 +02:00
1b431c3cfe Merge branch 'master' into strand_editmode 2016-07-19 11:42:52 +02:00
dd1211ecb8 Merge branch 'master' into strand_editmode 2016-07-19 10:32:23 +02:00
1d28579daf Merge branch 'mesh_samples' into strand_editmode 2016-07-14 09:59:52 +02:00
48fc8b76b2 Merge branch 'master' into strand_editmode 2016-07-14 09:57:38 +02:00
bd4ca21b54 Merge branch 'master' into mesh_samples 2016-07-14 09:56:58 +02:00
28270f0953 Replace the deprecated TessFace usage for mesh sampling by LoopTri.
Also make sure a valid generator instance for random sampling is always
returned, regardless of triangle counts.
2016-07-14 09:55:18 +02:00
2f596756b7 Merge branch 'master' into strand_editmode 2016-07-13 16:50:23 +02:00
56ad4520cd Merge branch 'master' into strand_editmode 2016-07-09 09:09:47 +02:00
4e95617769 Merge branch 'master' into strand_editmode 2016-07-05 09:53:24 +02:00
548dfdbd31 Merge branch 'master' into mesh_samples 2016-06-28 11:17:44 +02:00
73140ca283 Merge branch 'master' into strand_editmode 2016-06-27 10:52:10 +02:00
32455e230a Fix unused function warning. 2016-04-20 12:34:04 +02:00
af594b7b85 Fix meamleak from unfreed edit data when exiting in hair edit mode. 2016-04-19 14:19:54 +02:00
5f67ac83bf Fix unfreed temporary buffers in strand relaxation. 2016-04-19 14:05:35 +02:00
b2cb4c448e Define BMEditStrands struct as an extension of BMEditMesh.
The advantage is that we can now use this struct in the (rather involved)
process of generating a display DerivedMesh and apply modifiers to it.
2016-04-19 14:01:41 +02:00
216aacbcba Disable regular mesh drawing in hair edit mode. 2016-04-19 13:27:20 +02:00
ec51e83798 Proof-of-concept: Hair edit data from regular meshes.
This adds the possibility to edit a regular mesh like strand data.
It's more of a test for independence from particles than a real use case,
but could come in handy anyway.
2016-04-18 18:13:26 +02:00
9a20f68295 Check mesh samples for zero weights on eval to detect invalid/unusable samples. 2016-04-18 17:48:25 +02:00
a85c9b241a Corrected implementation of the strand relaxation method ported from particles.
This is still the same algorithm as in particle edit mode, but cleaned up
some unnecessary complications and use much more meaningful variable names.
2016-04-17 15:08:46 +02:00
07221e980b Merge branch 'master' into strand_editmode 2016-04-16 18:10:37 +02:00
56d8228713 Merge branch 'master' into mesh_samples 2015-10-11 15:51:07 +02:00
892529029f Port over the improved mesh sampling API from the mesh_samples branch. 2015-09-19 17:33:37 +02:00
d820eb1265 Merge branch 'master' into mesh_samples 2015-09-19 16:59:26 +02:00
3aaca08aad Merge branch 'master' into strand_editmode
Conflicts:
	source/blender/editors/sculpt_paint/paint_ops.c
2015-09-19 16:29:04 +02:00
5f6d9e301e Implementation of Volume Sampling on meshes.
Based on the ray casting technique described here: http://www.joesfer.com/?p=84

Volume samples currently are not bound to mesh deformation in any way, they are
simple object-space locations. Potentially a harmonic weight approach as in
mesh cages could be used.
2015-08-21 16:45:36 +02:00
05281abca1 Merge branch 'master' into mesh_samples
Conflicts:
	release/datafiles/locale
	release/scripts/addons
	source/blender/blenkernel/CMakeLists.txt
2015-08-21 15:07:47 +02:00
38d9301674 Another fix for mirror editing of strand data.
Currently strand edit mode tool use the object data 'x mirror' flag.
This flag is only available on mesh objects, but the strand editing
can be used for dupli override caches etc. as well. Eventually strand
editing should get it's own independent flag to avoid this problem.
2015-05-05 14:27:44 +02:00
fbc70fa51c Fix for call to mirror functions when mirroring is not enabled, giving
warnings.
2015-05-05 14:03:59 +02:00
d52bdb6d3d Moved particle related functions of the strands edit mode into a
separate file.

This may seem a bit like overkill, but it helps ensure that no particle
depedencies messes up the strand editing code. The same will be done for
other use cases of the strand editing code in the future.
2015-05-04 18:37:45 +02:00
16b6d6c676 Added missing CD layer type name for MSurfaceSample. 2015-05-04 17:52:13 +02:00
bf7e8b42fa Merge branch 'master' into strand_editmode
Conflicts:
	source/blender/bmesh/bmesh_class.h
	source/blender/bmesh/intern/bmesh_mesh_conv.h
2015-05-04 17:14:25 +02:00
20e960215a Particle shapekey code is only in the gooseberry branch, disabled here. 2015-04-20 12:59:01 +02:00
9cafd3ae56 SimDebugData is now global, for easier usage and less intrusive code. 2015-04-20 12:58:16 +02:00
0dccffb7aa Fix for CustomDataType syntax error from merging. 2015-04-20 12:37:21 +02:00
7719a6365a Use the X mirror option in new strand edit stroke brush tools.
Note that currently this has virtually no effect, because the mirror
option relies on exact positions of vertices, which does not happen with
random hair placement (the add brush has no mirror option yet).

Eventually topological mirroring should help with this case, but is not
implemented for either old or new strand edit yet.
2015-04-20 12:25:18 +02:00
3a3327dcd5 Cleanup: rename function argument for consistency. 2015-04-20 12:25:18 +02:00
8ffcd1f434 Utility methods for mirrored strand editing.
These are a modified version of their BMEditMesh counterparts.

use_topology is not yet implemented for strands. Native strand topology
is not very useful for this. Instead, the topology of the scalp mesh
should be used for finding mirrored strand roots, then the arc- or
parametric length of a vertex from the root to find mirrored verts.

Conflicts:
	source/blender/blenkernel/BKE_editstrands.h
2015-04-20 12:25:15 +02:00
ed1dc43657 Fix for strands undo creating an invalid BMesh.
When copying mesh data to bmesh the MVERT and similar customdata types
have to be omitted. Otherwise the bmesh instance ends up with NULL
pointers in customdata layers, but entries in the typemap != -1. The
effect was that when storing new steps after one or more undo, the
resulting original data would be copied, and subsequent undo steps
are ignored.
2015-04-20 12:24:34 +02:00
5b9b779cab Minor code reshuffling. 2015-04-20 12:24:34 +02:00
4798464e8b Extended Mesh <-> BMesh copy function versions for specifying custom
data masks explicitly.

A dummy mesh is used for strand edit undo storage like in mesh edit
to prevent unnecessary code duplication. However, when copying from/to
BMesh only the mesh data layers are copied by default, omitting the new
data layers for strands (currently only MSurfaceSample hair root data).
2015-04-20 12:24:33 +02:00
299859e40b Sanity check: if there is no edited/active object for an undo step,
consider it invalid and clean up.
2015-04-20 12:24:33 +02:00
5e42aff397 Basic undo support in the new strand edit mode.
This uses the generalized undo stack system which is also used for
object data edit. An extension is necessary to tell the undo functions
which object is specifically used for generating undo data and
identifying the stack, since strand editing does not edit the obdata
itself.
2015-04-20 12:24:33 +02:00
3f539c987f Removed deprecated UI button for the hair debug flag. 2015-04-20 12:24:33 +02:00
6146b90312 Select Linked operator for hair edit mode (select all vertices of a
strand).
2015-04-20 12:24:03 +02:00
8b2ec99f57 Lasso Select operator for hair edit mode. 2015-04-20 12:24:03 +02:00
410998cdc6 Border Select operator for hair edit mode. 2015-04-20 12:24:03 +02:00
b6f4e0932d Mouse Select operator for hair edit mode. 2015-04-20 12:24:03 +02:00
62fa5c4a84 Select/Deselect All operator for hair edit mode. 2015-04-20 12:24:03 +02:00
2cddc3cda8 Disabled the partial IK strand relaxation for the time being.
First goal is to reproduce current particle edit tools in the strand
edit mode, then make improvements.
2015-04-20 12:24:03 +02:00
9120df6cb3 Missing bmesh include path in scons files. 2015-04-20 12:24:02 +02:00
48a86af388 IK solver for hair strands that provides a better solution for keeping
consistent segment lengths when transforming vertices.

Warning: The implementation is not correct yet, but all the steps should
be there.

The main idea is to treat strands as a sequence of joints that are
displaced out of their original locations by a transform or other tool.
The solver then tries to find a global per-strand solution that keeps
the segment lengths unmodified, with a minimum change in angles from
the original starting shape. Such a solution is much more usable and
efficient than the current O(n^2) attempt of "spreading the error"
across the strand.

The inverse kinematics method is very flexible. It can also include
stretching, which would be very welcome for features like the length
tool. Different parts of the strand could be weighted separately using
scaling factors for the angle/stretch parameters.

Conflicts:
	source/blender/physics/intern/implicit.h
2015-04-20 12:24:00 +02:00
eacc24ccf1 Ported over the relaxation method for hair vertices from particle edit
mode.

This method is simple, but not really very usable. It works by
successively relaxing segments that are too long or too short, moving
both vertices along the edge between them. This is repeated N^2 times
(N: number of vertices on the strand).

A true IK solver could give a lot better results, as well as providing
many opportunities to apply weighting for targets (e.g. preferring to
move non-selected over selected vertices). Many different methods for
simple IK solvers exist, so there should be one that works well for
large number of simple strands. See e.g.
http://www.math.ucsd.edu/~sbuss/ResearchWeb/ikmethods/iksurvey.pdf
2015-04-20 12:23:49 +02:00
ac54ded29b Primitive transform operator support for strand vertices.
This does not yet prevent root transforms and/or length changes.
2015-04-20 12:23:49 +02:00
f7511b3d01 Improved hair combing tool, adjusting edge directions instead of
vertex positions.

This works a lot better with strokes perpendicular to the general hair
strand direction. With the previous comb tool such strokes would only
make a small dent in the hair curve and then vertices would slip out
of the tool circle. The edge combing affects the local direction of
strands, which acts as a kind of grabbing functionality by moving
further vertices in front of the tool circle. The result is that
drawing a curvy hair shape with the comb becomes much easier.

In addition, the new tool also uses edge filtering and weighting rather
than vertices. This means that small brushes also work well, instead
of having no effect when hitting the edge segments between vertices.

Further improvement could be achieved later by using a global strand
solver, which adjusts vertex positions based on an error metric along
the whole of the strand.
2015-04-20 12:23:48 +02:00
208ddcde2c Use a simple low-pass filter to generate a smoother, more stable
direction vector for the hair stroke tool.

This is necessary for directional tools such as combing, where the
stroke direction can lead to unwanted results if it changes too
abruptly.
2015-04-20 12:23:48 +02:00
f3b22c5769 Support for sim_debug drawing in hair edit mode (dev feature). 2015-04-20 12:23:48 +02:00
b70c815ac2 Renamed the edithair files in BKE to editstrands.
The rationale behind this is that the BKE code could be used for
modeling hair and fur as well as a number of other features such as
grass. The primary addition to BMesh is the limitation to strand-like
topology (simple vertex chains, optionally rooted on a mesh surface).

The editor code OTOH is quite hair specific, since the result should
be suitable for hair simulation, and the workflow should mimick actual
hair grooming intuitively. Eventually the hair edit mode could become
a generalized strand edit mode with slightly different tool sets for
various purposes, but for now it is quite specifically built for hair.

Conflicts:
	source/blender/blenkernel/CMakeLists.txt
	source/blender/blenkernel/intern/particle.c
2015-04-20 12:23:46 +02:00
d0a1fc8bb0 Sanity check for strand root/tip checks, now supports single vertices. 2015-04-20 12:23:23 +02:00
2d1d909817 MSVC compiler cannot handle standard C code.
Conflicts:
	source/blender/editors/physics/particle_edit.c
2015-04-20 12:23:20 +02:00
e9737da5f2 Fix scons build files for new hair edit code parts. 2015-04-20 12:23:16 +02:00
1e047231c6 Fix for hair-to-particle export: need to set weight=1.0 for hair roots,
since the particle system uses this for pinning them to the mesh.
2015-04-20 12:23:16 +02:00
142ef0b2e9 Fixed lib-linking for hair edit settings brush and object pointers. 2015-04-20 12:23:15 +02:00
56a09434c5 Basic Add tool for creating new strands in hair edit mode. 2015-04-20 12:23:15 +02:00
8672304e30 Fix for uninitialized return value. 2015-04-20 12:23:15 +02:00
f95dde244e Better feedback about valid sample from mesh sampling methods.
Conflicts:
	source/blender/editors/object/object_shapekey.c
2015-04-20 12:23:15 +02:00
79e649a3bf New simple storage method for single sample values. 2015-04-20 12:23:15 +02:00
e5b25e3c07 Generic raycast method for generating mesh surface samples.
Conflicts:
	source/blender/blenkernel/intern/mesh_sample.c
2015-04-20 12:23:15 +02:00
a4664ef88d New mesh sample evaluation function for shape key data. 2015-04-20 12:23:15 +02:00
be312b1399 First selection operator implementation for hair edit: circle select. 2015-04-20 12:23:14 +02:00
3edc512888 Show selection state of hair vertices with the usual theme colors. 2015-04-20 12:23:14 +02:00
b2ea8c1022 Added back different selection modes (strands, verts, tips) for hair
editing.
2015-04-20 12:23:14 +02:00
339a8b7521 Fix for OpenlGL state restoration after strand drawing, disable color
material option.
2015-04-20 12:23:14 +02:00
77da317cb1 Apply the same weight profile in the comb tool as in particle edit mode.
The comb tool will likely get a complete overhaul, but until then it
should just behave like the old tool.
2015-04-20 12:23:14 +02:00
2c0616b034 Support for common operators, keymap entries and radial controls for
hair brush settings.
2015-04-20 12:23:14 +02:00
a9fbd3b23b Fix for brush radius, the Brush.size property is actually the radius and
not the diameter.
2015-04-20 12:23:14 +02:00
687b5a1ec0 Implemented basic cursor drawing for hair edit mode brushes. 2015-04-20 12:23:13 +02:00
59efe23830 Primitive hair lighting in OpenGL solid shading, cleanup of strands
drawing code for edit mode.

The lighting uses the same method as the particle hair, interpreting
strand direction as the normal. This is nowhere near realistic hair
shading, but doing this with line shading alone in the fixed-function
pipeline is probably not possible. A GLSL shader could be used instead
for a more realistic alternative as part of the viewport project. At
least this simple shading mode gives some orientation feedback while
editing hair.
2015-04-20 12:23:13 +02:00
d16599ea04 Display basic brush properties in the toolbar for hair edit mode. 2015-04-20 12:23:13 +02:00
37d3a3331e Use the "official" brush size calculated by blenkernel and subdivide
the stroke step to avoid tunneling.
2015-04-20 12:23:13 +02:00
5254df30ba Filter affected hair vertices in edit mode based on the brush size and
z depth.
2015-04-20 12:23:13 +02:00
2590231315 Constrain hair root vertices to their mesh location after applying tools.
Conflicts:
	source/blender/bmesh/intern/bmesh_interp.c
	source/blender/bmesh/intern/bmesh_interp.h
2015-04-20 12:23:11 +02:00
83b20c7ddf Enforce hair constraints (constant segment length) after applying a tool.
Currently uses the same approach as old particle edit mode (rescale
hair segments from the root on). A more sophisticated approach using
least-square error minimization of the displacement could yield better
results.
2015-04-20 12:22:10 +02:00
0e06a60168 Make sure tool input data is in object space. 2015-04-20 12:22:10 +02:00
121677d7c8 Nicer vector math for mouse coordinates. 2015-04-20 12:22:10 +02:00
1615da133f Simplified first version of the hair stroke combing functionality.
Stroke tools will be categorized by the hair elements they operate on:
vertices, segments or strands (roots). In addition to that a filter
function defines the influence of the brush. This should be defined by
the other brush settings and be largely independent of the main tool
mode.
2015-04-20 12:22:10 +02:00
7ae6492c85 Lots of small fixes for paint system quirks to make hair brushes work
in the UI.

Conflicts:
	release/scripts/startup/bl_ui/space_view3d.py
	source/blender/editors/transform/manipulator_widget.c
2015-04-20 12:21:58 +02:00
7779166313 Define hair edit settings and brush types in the RNA, similar to paint
and sculpt settings.

Conflicts:
	source/blender/makesdna/DNA_brush_types.h
2015-04-20 12:19:28 +02:00
833a11f891 New image file icons for hair brush tools and an according tool enum in
the Brush RNA.
2015-04-20 12:17:16 +02:00
79959152e6 Calculate some basic info about mouse strokes and context and pass it
on to a general hair tool function.
2015-04-20 12:17:16 +02:00
4fd6a7a696 Revert previous hair edit operator scaffolding and port over the old
operator for strokes instead.

The paint system is much too complicated and does not add any real
advantage at this point.
2015-04-20 12:17:16 +02:00
98b15d9a7c Copied high-level operator code from the paint/sculpt system as a basis
for brush-stroke operators in hair edit mode.

Hopefully this can help avoid some redundant code and ensure consistent
keymapping etc.
2015-04-20 12:17:16 +02:00
a889a1458c Missing bmesh include folder for hair editor in scons. 2015-04-20 12:17:16 +02:00
7b6332f634 Apply the hair matrices when converting from particle keys to bmesh,
so the edit data is consistently in object space.
2015-04-20 12:17:16 +02:00
0a27e97fb7 Fix for GL buffer size for edges, needs to 2x for two vertex indices. 2015-04-20 12:17:16 +02:00
eb5257002b New drawing code for hair edit mode.
Conflicts:
	source/blender/editors/space_view3d/drawobject.c
	source/blender/editors/space_view3d/view3d_intern.h
2015-04-20 12:17:14 +02:00
23b284348b Removed deprecated code. 2015-04-20 12:15:17 +02:00
2d79994e39 Free strand edit memory after use. 2015-04-20 12:15:17 +02:00
fd34e3efbc Fix for verts-of-strand iterator: This requires a second pointer in
addition to the edge, otherwise the last point is omitted.
2015-04-20 12:15:17 +02:00
dc43df89cd CustomData layer for storing hair root locations as MSurfaceSamples.
This requires converting the old messy particle num/num_dmcache/fuv/foffset
data into the new mesh samples, which can potentially introduce floating
point errors and inaccuracies due to lack of face index mapping in the
new system. However, in any well-constructed particle system the hair
roots should be nearest to their num face, so mapping would be accurate
enough. If necessary face index data could be added to samples as a
legacy code hack, but probably it's best to eventually replace the
hair system as a whole anyway.
2015-04-20 12:15:17 +02:00
4e47239de2 Store the hair key weight in edit mode.
Currently particles only have a single weight value, controlling
pinning in the simulation (for root and virtual root verts) and goal
force scaling. This will be replaced by a full vertex group weight
system eventually.
2015-04-20 12:15:17 +02:00
991ee8a570 First customdata layer for particle mass.
This is more for testing purposes, since currently there is only a
single mass property for the psys as a whole. This should change in the
future though, to allow variable mass per strand or vertex.

Conflicts:
	source/blender/bmesh/intern/bmesh_interp.c
2015-04-20 12:15:15 +02:00
e43ef55e7f Basic particle data reconstruction when exiting edit mode. 2015-04-20 12:14:34 +02:00
1afd5be044 Fix for verts-of-strand iterator: After last edge step has to set the
edge to NULL to avoid infinite looping over the same edge (1-elem disk).
2015-04-20 12:14:34 +02:00
d550a29558 Fix for particle-to-bmesh: only was looping over totvert instead of
pa->totkey.
2015-04-20 12:14:34 +02:00
5f44f4a2ff Intermediate commit: switching strand edit data to BMesh.
Hair/Strand editing will only use a subset of the bmesh topology and
expect a specific topology that needs to be verified and enforced.
However, this extra requirement is much less work than reimplementing a
whole edit data system with the same feature set as bmesh and avoids
much redundant code.

Conflicts:
	source/blender/blenkernel/intern/customdata.c
	source/blender/makesdna/DNA_customdata_types.h
2015-04-20 12:14:30 +02:00
b0a9e48a19 New customdata type for mesh surface samples.
Conflicts:
	source/blender/makesdna/DNA_customdata_types.h
2015-04-20 12:14:23 +02:00
afb947c00c Switched the hair edit data to a bmesh-like structure for consistency.
This means using mempools to store curve and vertex data, which allows
arbitrary addition and removal of data more easily. Also this includes
an iterator system similar to bmesh iterators (although the simpler
topology makes it a lot less complex).

Conflicts:
	source/blender/blenkernel/intern/customdata.c
	source/blender/makesdna/DNA_customdata_types.h
2015-04-20 12:13:35 +02:00
f1e4d35489 Reconstruct most basic particle data when applying the hair edit, so
particle hairs remain visible.

Note that currently the hair root location (num/num_dmcache, fuv, foffset)
is not stored from edit data, so all hairs end up in a default location.
2015-04-20 12:12:01 +02:00
1c7053f7a0 Store hair edit data in particle systems and manage it in the operator
for entering/exiting the edit mode.

Conflicts:
	source/blender/blenkernel/intern/particle.c
2015-04-20 12:11:59 +02:00
7bb90a06e1 Moved code for the hair edit data structures to blenkernel.
This makes it work more like editmesh, and avoid the awkward and
basically bad-level approach in particles, where the edit data is an
anonymous pointer in particle systems together with a callback for
freeing.

Conflicts:
	source/blender/blenkernel/CMakeLists.txt
2015-04-20 12:11:44 +02:00
a9001adbb4 New edit mode for hair.
Conflicts:
	source/blender/blenloader/intern/readfile.c
2015-04-20 12:11:05 +02:00
dd69a3dbcf Finished the particle-to-edit conversion function. 2015-04-20 12:10:33 +02:00
c65f1b2e15 More work on particle conversion methods for hair edit mode. 2015-04-20 12:10:33 +02:00
09a7dbc29a New editor library for hair edit mode. 2015-04-20 12:10:33 +02:00
6078e79cea Minor syntax fix. 2015-04-20 12:10:33 +02:00
6783b15453 Shifted the dispatch from the algorithm side to the storage side of the
sampling system to keep the code simple.

Now there is a MSurfaceSampleStorage struct that encodes the storage
details, which the algorithms don't have to care about.
2015-04-20 12:10:33 +02:00
82132fc5b0 Added generalized method for creating an array of mesh samples with
arbitrary stride.
2015-04-20 12:10:32 +02:00
4b4f24607e Changed mesh sample definition to use 3 vertex weights instead of a
face index. This is easier to sample uniformly and avoids the need for
tesselation for evaluating.
2015-04-20 12:10:32 +02:00
fe48c353f1 Normalize the normal vector after sample eval to account for
interpolation.
2015-04-20 12:10:32 +02:00
308af3bfb3 Added a bool return to the eval function to give feedback on invalid
samples.
2015-04-20 12:10:32 +02:00
3f7d66ece4 Added a basic RNA definition for the mesh sampling system. 2015-04-20 12:10:32 +02:00
a952800ffc Eval function to get a location and normal vector from mesh samples. 2015-04-20 12:10:32 +02:00
71e34dad2b Usable random distribution algorithm.
Does not include area weighting yet.
2015-04-20 12:10:32 +02:00
24b77359ae Skeleton code for sampling meshes.
Conflicts:
	source/blender/blenkernel/CMakeLists.txt
2015-04-20 12:10:29 +02:00
62aabdd550 Merge remote-tracking branch 'github_lukastoenne/mesh_samples' into mesh_samples 2014-12-05 12:17:53 +01:00
2204bb092b Merge branch 'master' into mesh_samples 2014-12-05 11:23:38 +01:00
8374e08894 Mesh sampling system for unified point-on-mesh handling.
This code is not yet used by any feature in master, but has proved very useful in the Gooseberry branch and several WIP patches. Inclusion into master was requested to avoid diverging code in branches and get part of the Gooseberry code merged early on.

The core idea is to have a common way of identifying points on mesh surfaces. Efficient evaluation of such points after deformation and mappable modifiers (e.g. subdivision) tracks positions, normals and other surface attributes. This is used extensively in the particle/hair systems and could be useful for certain tools, duplicators, etc.. Currently the particle systems in particular are very fragile and succeptible to mesh changes because of the unreliable way mesh surface mapping is stored. While the new system at this point uses essentially the same method, it at least provides a centralized DNA type that can be improved easily without too much effort and compatiblity issues.

In addition to evaluating surface samples the system also provides a central place to implement surface sampling algorithms, such as random sampling, ray casting, paint-like tools, etc.

Differential Revision: https://developer.blender.org/D922
2014-12-01 15:10:16 +01:00
822949a3e2 Fix for uninitialized return value. 2014-12-01 14:42:18 +01:00
a86869af8f Better feedback about valid sample from mesh sampling methods.
Conflicts:
	source/blender/editors/object/object_shapekey.c
2014-12-01 14:41:02 +01:00
ca31ce144e New simple storage method for single sample values. 2014-12-01 14:38:03 +01:00
f61f7e2d57 Generic raycast method for generating mesh surface samples. 2014-12-01 14:36:10 +01:00
3036cedd90 New mesh sample evaluation function for shape key data. 2014-12-01 14:34:59 +01:00
cc5b3c2ff7 Merge branch 'mesh_samples' of github.com:lukastoenne/blender into mesh_samples 2014-12-01 14:29:41 +01:00
a30f9ffa87 Merge branch 'master' into mesh_samples
Conflicts:
	release/datafiles/locale
	release/scripts/addons
2014-12-01 14:29:09 +01:00
3361788b1c Merge branch 'master' into mesh_samples
Conflicts:
	release/datafiles/locale
	release/scripts/addons
2014-11-30 10:08:09 +01:00
21ab9ac9e3 Minor syntax fix. 2014-09-24 17:35:41 +02:00
18c6bb2436 Shifted the dispatch from the algorithm side to the storage side of the
sampling system to keep the code simple.

Now there is a MSurfaceSampleStorage struct that encodes the storage
details, which the algorithms don't have to care about.
2014-09-24 17:04:41 +02:00
a93841e3d4 Added generalized method for creating an array of mesh samples with
arbitrary stride.
2014-09-24 15:58:53 +02:00
728b6ed8e6 Merge branch 'master' into mesh_samples 2014-09-24 15:54:21 +02:00
fa0163b8c7 Changed mesh sample definition to use 3 vertex weights instead of a
face index. This is easier to sample uniformly and avoids the need for
tesselation for evaluating.
2014-03-04 15:39:48 +01:00
7112a531b2 Normalize the normal vector after sample eval to account for
interpolation.
2014-03-04 13:42:51 +01:00
60cde6ca4a Added a bool return to the eval function to give feedback on invalid
samples.
2014-03-04 13:42:06 +01:00
717db95210 Added a basic RNA definition for the mesh sampling system. 2014-03-03 17:57:56 +01:00
a944cf8450 Eval function to get a location and normal vector from mesh samples. 2014-03-03 14:10:58 +01:00
a1dd14c764 Usable random distribution algorithm.
Does not include area weighting yet.
2014-03-03 12:36:02 +01:00
47d024c2b0 Merge branch 'master' into mesh_samples 2014-03-03 11:56:02 +01:00
30e02b9a19 Skeleton code for sampling meshes. 2014-03-03 10:59:36 +01:00
158 changed files with 12188 additions and 218 deletions

View File

@@ -591,6 +591,7 @@ function(SETUP_BLENDER_SORTED_LIBS)
bf_editor_object
bf_editor_armature
bf_editor_physics
bf_editor_hair
bf_editor_render
bf_editor_scene
bf_editor_screen

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -163,6 +163,7 @@ class BRUSH_OT_active_index_set(Operator):
"vertex_paint": "use_paint_vertex",
"weight_paint": "use_paint_weight",
"image_paint": "use_paint_image",
"hair_edit": "use_hair_edit",
}
def execute(self, context):

View File

@@ -43,6 +43,7 @@ _modules = [
"properties_data_lightprobe",
"properties_data_speaker",
"properties_game",
"properties_hair",
"properties_mask_common",
"properties_material",
"properties_object",

View File

@@ -1528,6 +1528,24 @@ class DATA_PT_modifiers(ModifierButtonsPanel, Panel):
if md.rest_source == 'BIND':
layout.operator("object.correctivesmooth_bind", text="Unbind" if is_bind else "Bind")
def HAIR(self, layout, ob, md):
hair = md.hair
col = layout.column()
col.label(text="Follicles:")
col.label(text="Count: %d" % len(hair.follicles))
col.operator("object.hair_follicles_generate", text="Generate")
col = layout.column()
col.template_list("HAIR_UL_groups", "", hair, "groups", hair, "active_group_index")
layout.separator()
group = hair.active_group
if group:
col = layout.column()
col.prop(group, "type")
classes = (
DATA_PT_modifiers,

View File

@@ -0,0 +1,40 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import bpy
from bpy.types import UIList
class HAIR_UL_groups(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
group = item
if self.layout_type in {'DEFAULT', 'COMPACT'}:
layout.prop(group, "name", text="", emboss=False, icon_value=icon)
elif self.layout_type == 'GRID':
layout.alignment = 'CENTER'
layout.label(text="", icon_value=icon)
classes = (
HAIR_UL_groups,
)
if __name__ == "__main__": # only for live edit.
from bpy.utils import register_class
for cls in classes:
register_class(cls)

View File

@@ -38,10 +38,10 @@ class UnifiedPaintPanel:
elif context.image_paint_object:
if (toolsettings.image_paint and toolsettings.image_paint.detect_data()):
return toolsettings.image_paint
return None
elif context.particle_edit_object:
return toolsettings.particle_edit
elif context.hair_edit_object:
return toolsettings.hair_edit
return None

View File

@@ -52,6 +52,12 @@ class VIEW3D_HT_header(Header):
# Particle edit
if mode == 'PARTICLE_EDIT':
row.prop(toolsettings.particle_edit, "select_mode", text="", expand=True)
elif mode == 'HAIR_EDIT':
row.prop(toolsettings.hair_edit, "select_mode", text="", expand=True)
row.prop(toolsettings.hair_edit, "hair_draw_mode", text="", expand=True)
if toolsettings.hair_edit.hair_draw_mode == 'FIBERS':
row.prop(toolsettings.hair_edit, "hair_draw_size", text="Size")
row.prop(toolsettings.hair_edit, "hair_draw_subdivision", text="Subdivide")
# Occlude geometry
if ((view.viewport_shade not in {'BOUNDBOX', 'WIREFRAME'} and (mode == 'PARTICLE_EDIT' or (mode == 'EDIT' and obj.type == 'MESH'))) or
@@ -172,7 +178,7 @@ class VIEW3D_MT_editor_menus(Menu):
layout.menu("VIEW3D_MT_select_paint_mask")
elif mesh.use_paint_mask_vertex and mode_string == 'PAINT_WEIGHT':
layout.menu("VIEW3D_MT_select_paint_mask_vertex")
elif mode_string != 'SCULPT':
elif mode_string not in {'SCULPT'}:
layout.menu("VIEW3D_MT_select_%s" % mode_string.lower())
if gp_edit:
@@ -197,7 +203,7 @@ class VIEW3D_MT_editor_menus(Menu):
elif obj:
if mode_string != 'PAINT_TEXTURE':
layout.menu("VIEW3D_MT_%s" % mode_string.lower())
if mode_string in {'SCULPT', 'PAINT_VERTEX', 'PAINT_WEIGHT', 'PAINT_TEXTURE'}:
if mode_string in {'SCULPT', 'PAINT_VERTEX', 'PAINT_WEIGHT', 'PAINT_TEXTURE', 'HAIR'}:
layout.menu("VIEW3D_MT_brush")
if mode_string == 'SCULPT':
layout.menu("VIEW3D_MT_hide_mask")
@@ -1072,6 +1078,13 @@ class VIEW3D_MT_select_paint_mask_vertex(Menu):
layout.operator("paint.vert_select_ungrouped", text="Ungrouped Verts")
class VIEW3D_MT_select_hair(Menu):
bl_label = "Select"
def draw(self, context):
layout = self.layout
class VIEW3D_MT_angle_control(Menu):
bl_label = "Angle Control"
@@ -1712,7 +1725,7 @@ class VIEW3D_MT_brush(Menu):
return
# brush paint modes
layout.menu("VIEW3D_MT_brush_paint_modes")
layout.menu("VIEW3D_MT_brush_object_modes")
# brush tool
if context.sculpt_object:
@@ -1722,6 +1735,8 @@ class VIEW3D_MT_brush(Menu):
layout.prop_menu_enum(brush, "image_tool")
elif context.vertex_paint_object or context.weight_paint_object:
layout.prop_menu_enum(brush, "vertex_tool")
elif context.hair_edit_object:
layout.prop_menu_enum(brush, "hair_tool")
# TODO: still missing a lot of brush options here
@@ -1745,7 +1760,7 @@ class VIEW3D_MT_brush(Menu):
layout.operator("sculpt.set_persistent_base")
class VIEW3D_MT_brush_paint_modes(Menu):
class VIEW3D_MT_brush_object_modes(Menu):
bl_label = "Enabled Modes"
def draw(self, context):
@@ -1758,6 +1773,7 @@ class VIEW3D_MT_brush_paint_modes(Menu):
layout.prop(brush, "use_paint_vertex", text="Vertex Paint")
layout.prop(brush, "use_paint_weight", text="Weight Paint")
layout.prop(brush, "use_paint_image", text="Texture Paint")
layout.prop(brush, "use_hair_edit", text="Hair Edit")
# ********** Vertex paint menu **********
@@ -2022,6 +2038,14 @@ class VIEW3D_MT_particle_specials(Menu):
class VIEW3D_MT_particle_showhide(ShowHideMenu, Menu):
_operator_name = "particle"
# ********** Hair menu **********
class VIEW3D_MT_hair(Menu):
bl_label = "Hair"
def draw(self, context):
layout = self.layout
# ********** Pose Menu **********
@@ -3774,6 +3798,7 @@ classes = (
VIEW3D_MT_select_pose,
VIEW3D_MT_select_pose_more_less,
VIEW3D_MT_select_particle,
VIEW3D_MT_select_hair,
VIEW3D_MT_edit_mesh,
VIEW3D_MT_edit_mesh_select_similar,
VIEW3D_MT_edit_mesh_select_by_trait,
@@ -3814,7 +3839,7 @@ classes = (
VIEW3D_MT_make_links,
VIEW3D_MT_object_game,
VIEW3D_MT_brush,
VIEW3D_MT_brush_paint_modes,
VIEW3D_MT_brush_object_modes,
VIEW3D_MT_paint_vertex,
VIEW3D_MT_hook,
VIEW3D_MT_vertex_group,
@@ -3824,6 +3849,7 @@ classes = (
VIEW3D_MT_particle,
VIEW3D_MT_particle_specials,
VIEW3D_MT_particle_showhide,
VIEW3D_MT_hair,
VIEW3D_MT_pose,
VIEW3D_MT_pose_transform,
VIEW3D_MT_pose_slide,

View File

@@ -974,6 +974,21 @@ class VIEW3D_PT_tools_brush(Panel, View3DPaintPanel):
layout.row().prop(brush, "puff_mode", expand=True)
layout.prop(brush, "use_puff_volume")
# Hair Mode #
elif context.hair_edit_object and brush:
col = layout.column()
row = col.row(align=True)
self.prop_unified_size(row, context, brush, "size", slider=True, text="Radius")
self.prop_unified_size(row, context, brush, "use_pressure_size")
row = col.row(align=True)
self.prop_unified_strength(row, context, brush, "strength", text="Strength")
self.prop_unified_strength(row, context, brush, "use_pressure_strength")
col.prop(brush, "hair_tool", text="Tool")
# Sculpt Mode #
elif context.sculpt_object and brush:
@@ -1958,6 +1973,23 @@ class VIEW3D_PT_tools_particlemode(View3DPanel, Panel):
sub.prop(pe, "fade_frames", slider=True)
class VIEW3D_PT_tools_hairmode(View3DPanel, Panel):
"""Tools for hair mode"""
bl_context = "hairmode"
bl_label = "Options"
bl_category = "Tools"
def draw(self, context):
layout = self.layout
settings = context.tool_settings.hair_edit
ob = context.active_object
if ob.data:
col = layout.column(align=True)
col.prop(ob.data, "use_mirror_x")
# Grease Pencil drawing tools
class VIEW3D_PT_tools_grease_pencil_draw(GreasePencilDrawingToolsPanel, Panel):
bl_space_type = 'VIEW_3D'

View File

@@ -116,6 +116,7 @@ enum {
CTX_MODE_PAINT_VERTEX,
CTX_MODE_PAINT_TEXTURE,
CTX_MODE_PARTICLE,
CTX_MODE_HAIR,
CTX_MODE_OBJECT
};

View File

@@ -57,6 +57,8 @@ extern const CustomDataMask CD_MASK_EDITMESH;
extern const CustomDataMask CD_MASK_DERIVEDMESH;
extern const CustomDataMask CD_MASK_BMESH;
extern const CustomDataMask CD_MASK_FACECORNERS;
extern const CustomDataMask CD_MASK_STRANDS;
extern const CustomDataMask CD_MASK_STRANDS_BMESH;
extern const CustomDataMask CD_MASK_EVERYTHING;
/* for ORIGINDEX layer type, indicates no original index for this element */
@@ -269,6 +271,7 @@ void *CustomData_get(const struct CustomData *data, int index, int type);
void *CustomData_get_n(const struct CustomData *data, int type, int index, int n);
void *CustomData_bmesh_get(const struct CustomData *data, void *block, int type);
void *CustomData_bmesh_get_n(const struct CustomData *data, void *block, int type, int n);
void *CustomData_bmesh_get_named(const struct CustomData *data, void *block, int type, const char *name);
/* gets the layer at physical index n, with no type checking.
*/

View File

@@ -0,0 +1,106 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) Blender Foundation
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Lukas Toenne
*
* ***** END GPL LICENSE BLOCK *****
*/
#ifndef __BKE_EDITSTRANDS_H__
#define __BKE_EDITSTRANDS_H__
/** \file blender/blenkernel/BKE_editstrands.h
* \ingroup bke
*/
#include "BLI_utildefines.h"
#include "DNA_customdata_types.h"
#include "BKE_customdata.h"
#include "BKE_editmesh.h"
#include "bmesh.h"
struct BMesh;
struct DerivedMesh;
struct Mesh;
struct Object;
typedef struct BMEditStrands {
BMEditMesh base;
/* Scalp mesh for fixing root vertices */
struct DerivedMesh *root_dm;
int flag;
unsigned int vertex_glbuf; // legacy gpu code
unsigned int elem_glbuf; // legacy gpu code
unsigned int dot_glbuf; // legacy gpu code
void *batch_cache;
} BMEditStrands;
/* BMEditStrands->flag */
typedef enum BMEditStrandsFlag {
BM_STRANDS_DIRTY_SEGLEN = (1 << 0),
BM_STRANDS_DIRTY_ROOTS = (1 << 1),
} BMEditStrandsFlag;
struct BMEditStrands *BKE_editstrands_create(struct BMesh *bm, struct DerivedMesh *root_dm);
struct BMEditStrands *BKE_editstrands_copy(struct BMEditStrands *es);
struct BMEditStrands *BKE_editstrands_from_object_particles(struct Object *ob, struct ParticleSystem **r_psys);
struct BMEditStrands *BKE_editstrands_from_object(struct Object *ob);
void BKE_editstrands_update_linked_customdata(struct BMEditStrands *es);
void BKE_editstrands_free(struct BMEditStrands *es);
/* === Constraints === */
/* Stores vertex locations for temporary reference:
* Vertex locations get modified by tools, but then need to be corrected
* by calculating a smooth solution based on the difference to original pre-tool locations.
*/
typedef float (*BMEditStrandsLocations)[3];
BMEditStrandsLocations BKE_editstrands_get_locations(struct BMEditStrands *edit);
void BKE_editstrands_free_locations(BMEditStrandsLocations locs);
void BKE_editstrands_solve_constraints(struct Object *ob, struct BMEditStrands *es, BMEditStrandsLocations orig);
void BKE_editstrands_ensure(struct BMEditStrands *es);
/* === Particle Conversion === */
struct BMesh *BKE_editstrands_particles_to_bmesh(struct Object *ob, struct ParticleSystem *psys);
void BKE_editstrands_particles_from_bmesh(struct Object *ob, struct ParticleSystem *psys);
/* === Mesh Conversion === */
struct BMesh *BKE_editstrands_mesh_to_bmesh(struct Object *ob, struct Mesh *me);
void BKE_editstrands_mesh_from_bmesh(struct Object *ob);
/* === Draw Cache === */
enum {
BKE_STRANDS_BATCH_DIRTY_ALL = 0,
BKE_STRANDS_BATCH_DIRTY_SELECT = 1,
};
void BKE_editstrands_batch_cache_dirty(struct BMEditStrands *es, int mode);
void BKE_editstrands_batch_cache_free(struct BMEditStrands *es);
#endif

View File

@@ -0,0 +1,94 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) Blender Foundation
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Lukas Toenne
*
* ***** END GPL LICENSE BLOCK *****
*/
#ifndef __BKE_HAIR_H__
#define __BKE_HAIR_H__
/** \file blender/blenkernel/BKE_hair.h
* \ingroup bke
*/
#include "BLI_utildefines.h"
struct HairFollicle;
struct HairPattern;
struct HairGroup;
struct DerivedMesh;
static const unsigned int STRAND_INDEX_NONE = 0xFFFFFFFF;
struct HairPattern* BKE_hair_new(void);
struct HairPattern* BKE_hair_copy(struct HairPattern *hair);
void BKE_hair_free(struct HairPattern *hair);
void BKE_hair_set_num_follicles(struct HairPattern *hair, int count);
void BKE_hair_follicles_generate(struct HairPattern *hair, struct DerivedMesh *scalp, int count, unsigned int seed);
struct HairGroup* BKE_hair_group_new(struct HairPattern *hair, int type);
void BKE_hair_group_remove(struct HairPattern *hair, struct HairGroup *group);
struct HairGroup* BKE_hair_group_copy(struct HairPattern *hair, struct HairGroup *group);
void BKE_hair_group_moveto(struct HairPattern *hair, struct HairGroup *group, int position);
void BKE_hair_group_name_set(struct HairPattern *hair, struct HairGroup *group, const char *name);
void BKE_hair_update_groups(struct HairPattern *hair);
/* === Draw Buffer Texture === */
typedef struct HairDrawDataInterface {
const struct HairGroup *group;
struct DerivedMesh *scalp;
int (*get_num_strands)(const struct HairDrawDataInterface* hairdata);
int (*get_num_verts)(const struct HairDrawDataInterface* hairdata);
void (*get_strand_lengths)(const struct HairDrawDataInterface* hairdata, int *r_lengths);
void (*get_strand_roots)(const struct HairDrawDataInterface* hairdata, struct MeshSample *r_roots);
void (*get_strand_vertices)(const struct HairDrawDataInterface* hairdata, float (*r_positions)[3]);
} HairDrawDataInterface;
int* BKE_hair_strands_get_fiber_lengths(const struct HairDrawDataInterface *hairdata, int subdiv);
void BKE_hair_strands_get_texture_buffer_size(const struct HairDrawDataInterface *hairdata, int subdiv,
int *r_size, int *r_strand_map_start,
int *r_strand_vertex_start, int *r_fiber_start);
void BKE_hair_strands_get_texture_buffer(const struct HairDrawDataInterface *hairdata, int subdiv, void *texbuffer);
/* === Draw Cache === */
enum {
BKE_HAIR_BATCH_DIRTY_ALL = 0,
};
void BKE_hair_batch_cache_dirty(struct HairGroup *group, int mode);
void BKE_hair_batch_cache_all_dirty(struct HairPattern *hair, int mode);
void BKE_hair_batch_cache_free(struct HairGroup *group);
int* BKE_hair_group_get_fiber_lengths(struct HairGroup *group, struct DerivedMesh *scalp, int subdiv);
void BKE_hair_group_get_texture_buffer_size(struct HairGroup *group, struct DerivedMesh *scalp, int subdiv,
int *r_size, int *r_strand_map_start, int *r_strand_vertex_start, int *r_fiber_start);
void BKE_hair_group_get_texture_buffer(struct HairGroup *group, struct DerivedMesh *scalp, int subdiv, void *texbuffer);
#endif

View File

@@ -0,0 +1,110 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* ***** END GPL LICENSE BLOCK *****
*/
#ifndef __BKE_MESH_SAMPLE_H__
#define __BKE_MESH_SAMPLE_H__
/** \file BKE_mesh_sample.h
* \ingroup bke
*/
struct DerivedMesh;
struct Key;
struct KeyBlock;
struct MFace;
struct MVert;
struct MeshSample;
struct MeshSampleGenerator;
typedef struct MeshSampleGenerator MeshSampleGenerator;
typedef float (*MeshSampleVertexWeightFp)(struct DerivedMesh *dm, struct MVert *vert, unsigned int index, void *userdata);
typedef void* (*MeshSampleThreadContextCreateFp)(void *userdata, int start);
typedef void (*MeshSampleThreadContextFreeFp)(void *userdata, void *thread_ctx);
typedef bool (*MeshSampleRayFp)(void *userdata, void *thread_ctx, float ray_start[3], float ray_end[3]);
/* ==== Utility Functions ==== */
float* BKE_mesh_sample_calc_triangle_weights(struct DerivedMesh *dm, MeshSampleVertexWeightFp vertex_weight_cb, void *userdata, float *r_area);
/* ==== Evaluate ==== */
bool BKE_mesh_sample_is_volume_sample(const struct MeshSample *sample);
bool BKE_mesh_sample_eval(struct DerivedMesh *dm, const struct MeshSample *sample, float loc[3], float nor[3], float tang[3]);
bool BKE_mesh_sample_shapekey(struct Key *key, struct KeyBlock *kb, const struct MeshSample *sample, float loc[3]);
void BKE_mesh_sample_clear(struct MeshSample *sample);
/* ==== Generator Types ==== */
struct MeshSampleGenerator *BKE_mesh_sample_gen_surface_vertices(void);
/* vertex_weight_cb is optional */
struct MeshSampleGenerator *BKE_mesh_sample_gen_surface_random(unsigned int seed, bool use_area_weight,
MeshSampleVertexWeightFp vertex_weight_cb, void *userdata);
struct MeshSampleGenerator *BKE_mesh_sample_gen_surface_raycast(
MeshSampleThreadContextCreateFp thread_context_create_cb,
MeshSampleThreadContextFreeFp thread_context_free_cb,
MeshSampleRayFp ray_cb,
void *userdata);
struct MeshSampleGenerator *BKE_mesh_sample_gen_surface_poissondisk(unsigned int seed, float mindist, unsigned int max_samples,
MeshSampleVertexWeightFp vertex_weight_cb, void *userdata);
struct MeshSampleGenerator *BKE_mesh_sample_gen_volume_random_bbray(unsigned int seed, float density);
void BKE_mesh_sample_free_generator(struct MeshSampleGenerator *gen);
/* ==== Sampling ==== */
void BKE_mesh_sample_generator_bind(struct MeshSampleGenerator *gen, struct DerivedMesh *dm);
void BKE_mesh_sample_generator_unbind(struct MeshSampleGenerator *gen);
unsigned int BKE_mesh_sample_gen_get_max_samples(const struct MeshSampleGenerator *gen);
/* Generate a single sample.
* Not threadsafe!
*/
bool BKE_mesh_sample_generate(struct MeshSampleGenerator *gen, struct MeshSample *sample);
/* Generate a large number of samples.
*/
int BKE_mesh_sample_generate_batch_ex(struct MeshSampleGenerator *gen,
void *output_buffer, int output_stride, int count,
bool use_threads);
int BKE_mesh_sample_generate_batch(struct MeshSampleGenerator *gen,
MeshSample *output_buffer, int count);
/* ==== Utilities ==== */
struct ParticleSystem;
struct ParticleData;
struct BVHTreeFromMesh;
bool BKE_mesh_sample_from_particle(struct MeshSample *sample, struct ParticleSystem *psys, struct DerivedMesh *dm, struct ParticleData *pa);
bool BKE_mesh_sample_to_particle(struct MeshSample *sample, struct ParticleSystem *psys, struct DerivedMesh *dm, struct BVHTreeFromMesh *bvhtree, struct ParticleData *pa);
#endif /* __BKE_MESH_SAMPLE_H__ */

View File

@@ -420,6 +420,7 @@ void psys_interpolate_face(struct MVert *mvert, struct MFace *mface, struct MTFa
float orco[3], float ornor[3]);
float psys_particle_value_from_verts(struct DerivedMesh *dm, short from, struct ParticleData *pa, float *values);
void psys_get_from_key(struct ParticleKey *key, float loc[3], float vel[3], float rot[4], float *time);
int psys_get_index_on_dm(struct ParticleSystem *psys, struct DerivedMesh *dm, ParticleData *pa, int *mapindex, float mapfw[4]);
/* BLI_bvhtree_ray_cast callback */
void BKE_psys_collision_neartest_cb(void *userdata, int index, const struct BVHTreeRay *ray, struct BVHTreeRayHit *hit);

View File

@@ -103,6 +103,7 @@ set(SRC
intern/editmesh.c
intern/editmesh_bvh.c
intern/editmesh_tangent.c
intern/editstrands.c
intern/effect.c
intern/fcurve.c
intern/fluidsim.c
@@ -111,6 +112,8 @@ set(SRC
intern/freestyle.c
intern/gpencil.c
intern/group.c
intern/hair.c
intern/hair_draw.c
intern/icons.c
intern/idcode.c
intern/idprop.c
@@ -135,6 +138,7 @@ set(SRC
intern/mesh_evaluate.c
intern/mesh_mapping.c
intern/mesh_remap.c
intern/mesh_sample.c
intern/mesh_tangent.c
intern/mesh_validate.c
intern/modifier.c
@@ -233,6 +237,7 @@ set(SRC
BKE_deform.h
BKE_displist.h
BKE_dynamicpaint.h
BKE_editstrands.h
BKE_editmesh.h
BKE_editmesh_bvh.h
BKE_editmesh_tangent.h
@@ -244,6 +249,7 @@ set(SRC
BKE_global.h
BKE_gpencil.h
BKE_group.h
BKE_hair.h
BKE_icons.h
BKE_idcode.h
BKE_idprop.h
@@ -265,6 +271,7 @@ set(SRC
BKE_mesh.h
BKE_mesh_mapping.h
BKE_mesh_remap.h
BKE_mesh_sample.h
BKE_mesh_tangent.h
BKE_modifier.h
BKE_movieclip.h

View File

@@ -70,7 +70,7 @@ static void brush_defaults(Brush *brush)
brush->blend = 0;
brush->flag = 0;
brush->ob_mode = OB_MODE_ALL_PAINT;
brush->ob_mode = OB_MODE_ALL_BRUSH;
/* BRUSH SCULPT TOOL SETTINGS */
brush->weight = 1.0f; /* weight of brush 0 - 1.0 */

View File

@@ -1002,6 +1002,7 @@ int CTX_data_mode_enum_ex(const Object *obedit, const Object *ob)
else if (ob->mode & OB_MODE_VERTEX_PAINT) return CTX_MODE_PAINT_VERTEX;
else if (ob->mode & OB_MODE_TEXTURE_PAINT) return CTX_MODE_PAINT_TEXTURE;
else if (ob->mode & OB_MODE_PARTICLE_EDIT) return CTX_MODE_PARTICLE;
else if (ob->mode & OB_MODE_HAIR_EDIT) return CTX_MODE_HAIR;
}
}
@@ -1031,6 +1032,7 @@ static const char *data_mode_strings[] = {
"vertexpaint",
"imagepaint",
"particlemode",
"hairmode",
"objectmode",
NULL
};

View File

@@ -52,6 +52,7 @@
#include "BKE_customdata.h"
#include "BKE_customdata_file.h"
#include "BKE_editstrands.h"
#include "BKE_global.h"
#include "BKE_main.h"
#include "BKE_mesh_mapping.h"
@@ -1295,6 +1296,8 @@ static const LayerTypeInfo LAYERTYPEINFO[CD_NUMTYPES] = {
{sizeof(short[4][3]), "", 0, NULL, NULL, NULL, NULL, layerSwap_flnor, NULL},
/* 41: CD_CUSTOMLOOPNORMAL */
{sizeof(short[2]), "vec2s", 1, NULL, NULL, NULL, NULL, NULL, NULL},
/* 42: CD_MESH_SAMPLE */
{sizeof(MeshSample), "MeshSample", 1, NULL, NULL, NULL, NULL, NULL, NULL},
};
@@ -1310,7 +1313,7 @@ static const char *LAYERTYPENAMES[CD_NUMTYPES] = {
/* 30-34 */ "CDSubSurfCrease", "CDOrigSpaceLoop", "CDPreviewLoopCol", "CDBMElemPyPtr", "CDPaintMask",
/* 35-36 */ "CDGridPaintMask", "CDMVertSkin",
/* 37-38 */ "CDFreestyleEdge", "CDFreestyleFace",
/* 39-41 */ "CDMLoopTangent", "CDTessLoopNormal", "CDCustomLoopNormal",
/* 39-42 */ "CDMLoopTangent", "CDTessLoopNormal", "CDCustomLoopNormal", "CDMeshSample",
};
@@ -1355,6 +1358,17 @@ const CustomDataMask CD_MASK_FACECORNERS =
CD_MASK_ORIGSPACE | CD_MASK_ORIGSPACE_MLOOP |
CD_MASK_TESSLOOPNORMAL | CD_MASK_NORMAL |
CD_MASK_TANGENT | CD_MASK_MLOOPTANGENT;
const CustomDataMask CD_MASK_STRANDS =
CD_MASK_MVERT | CD_MASK_MEDGE |
CD_MASK_MDEFORMVERT | CD_MASK_MCOL |
CD_MASK_PROP_FLT | CD_MASK_PROP_INT | CD_MASK_PROP_STR | CD_MASK_MDISPS |
CD_MASK_MVERT_SKIN | CD_MASK_FREESTYLE_EDGE |
CD_MASK_MSURFACE_SAMPLE;
const CustomDataMask CD_MASK_STRANDS_BMESH =
CD_MASK_MDEFORMVERT | CD_MASK_PROP_FLT | CD_MASK_PROP_INT |
CD_MASK_PROP_STR | CD_MASK_SHAPEKEY | CD_MASK_SHAPE_KEYINDEX | CD_MASK_MDISPS |
CD_MASK_MVERT_SKIN | CD_MASK_FREESTYLE_EDGE |
CD_MASK_MSURFACE_SAMPLE;
const CustomDataMask CD_MASK_EVERYTHING =
CD_MASK_MVERT | CD_MASK_MDEFORMVERT | CD_MASK_MEDGE | CD_MASK_MFACE |
CD_MASK_MTFACE | CD_MASK_MCOL | CD_MASK_ORIGINDEX | CD_MASK_NORMAL /* | CD_MASK_POLYINDEX */ | CD_MASK_PROP_FLT |
@@ -2900,6 +2914,18 @@ void *CustomData_bmesh_get_layer_n(const CustomData *data, void *block, int n)
return POINTER_OFFSET(block, data->layers[n].offset);
}
/*Bmesh Custom Data Functions. Should replace editmesh ones with these as well, due to more effecient memory alloc*/
void *CustomData_bmesh_get_named(const CustomData *data, void *block, int type, const char *name)
{
int layer_index;
/* get the layer index of the named layer of type */
layer_index = CustomData_get_named_layer_index(data, type, name);
if (layer_index == -1) return NULL;
return (char *)block + data->layers[layer_index].offset;
}
bool CustomData_layer_has_math(const struct CustomData *data, int layer_n)
{
const LayerTypeInfo *typeInfo = layerType_getInfo(data->layers[layer_n].type);

View File

@@ -0,0 +1,300 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) Blender Foundation
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Lukas Toenne
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/blenkernel/intern/editstrands.c
* \ingroup bke
*/
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_mempool.h"
#include "DNA_customdata_types.h"
#include "DNA_key_types.h"
#include "DNA_mesh_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "DNA_particle_types.h"
#include "BKE_bvhutils.h"
#include "BKE_customdata.h"
#include "BKE_cdderivedmesh.h"
#include "BKE_DerivedMesh.h"
#include "BKE_editstrands.h"
#include "BKE_effect.h"
#include "BKE_hair.h"
#include "BKE_mesh_sample.h"
#include "BKE_object.h"
#include "BKE_particle.h"
#include "BPH_strands.h"
#include "intern/bmesh_mesh_conv.h"
#include "intern/bmesh_strands_conv.h"
BMEditStrands *BKE_editstrands_create(BMesh *bm, DerivedMesh *root_dm)
{
BMEditStrands *es = MEM_callocN(sizeof(BMEditStrands), __func__);
es->base.bm = bm;
es->root_dm = CDDM_copy(root_dm);
BKE_editstrands_batch_cache_dirty(es, BKE_STRANDS_BATCH_DIRTY_ALL);
return es;
}
BMEditStrands *BKE_editstrands_copy(BMEditStrands *es)
{
BMEditStrands *es_copy = MEM_callocN(sizeof(BMEditStrands), __func__);
*es_copy = *es;
es_copy->base.bm = BM_mesh_copy(es->base.bm);
es_copy->root_dm = CDDM_copy(es->root_dm);
BKE_editstrands_batch_cache_dirty(es_copy, BKE_STRANDS_BATCH_DIRTY_ALL);
return es_copy;
}
/**
* \brief Return the BMEditStrands for a given object's particle systems
*/
BMEditStrands *BKE_editstrands_from_object_particles(Object *ob, ParticleSystem **r_psys)
{
ParticleSystem *psys = psys_get_current(ob);
if (psys && psys->hairedit) {
if (r_psys) {
*r_psys = psys;
}
return psys->hairedit;
}
if (r_psys) {
*r_psys = NULL;
}
return NULL;
}
/**
* \brief Return the BMEditStrands for a given object
*/
BMEditStrands *BKE_editstrands_from_object(Object *ob)
{
if (ob && ob->type == OB_MESH) {
Mesh *me = ob->data;
if (me->edit_strands)
return me->edit_strands;
}
return BKE_editstrands_from_object_particles(ob, NULL);
}
void BKE_editstrands_update_linked_customdata(BMEditStrands *UNUSED(es))
{
}
/*does not free the BMEditStrands struct itself*/
void BKE_editstrands_free(BMEditStrands *es)
{
BKE_editstrands_batch_cache_free(es);
if (es->base.bm)
BM_mesh_free(es->base.bm);
if (es->root_dm)
es->root_dm->release(es->root_dm);
}
/* === Constraints === */
BMEditStrandsLocations BKE_editstrands_get_locations(BMEditStrands *edit)
{
BMesh *bm = edit->base.bm;
BMEditStrandsLocations locs = MEM_mallocN(3*sizeof(float) * bm->totvert, "editstrands locations");
BMVert *v;
BMIter iter;
int i;
BM_ITER_MESH_INDEX(v, &iter, bm, BM_VERTS_OF_MESH, i) {
copy_v3_v3(locs[i], v->co);
}
return locs;
}
void BKE_editstrands_free_locations(BMEditStrandsLocations locs)
{
MEM_freeN(locs);
}
void BKE_editstrands_solve_constraints(Object *ob, BMEditStrands *es, BMEditStrandsLocations orig)
{
BKE_editstrands_ensure(es);
BPH_strands_solve_constraints(ob, es, orig);
BKE_editstrands_batch_cache_dirty(es, BKE_STRANDS_BATCH_DIRTY_ALL);
}
static void editstrands_calc_segment_lengths(BMesh *bm)
{
BMVert *root, *v, *vprev;
BMIter iter, iter_strand;
int k;
BM_ITER_STRANDS(root, &iter, bm, BM_STRANDS_OF_MESH) {
BM_ITER_STRANDS_ELEM_INDEX(v, &iter_strand, root, BM_VERTS_OF_STRAND, k) {
if (k > 0) {
float length = len_v3v3(v->co, vprev->co);
BM_elem_float_data_named_set(&bm->vdata, v, CD_PROP_FLT, CD_HAIR_SEGMENT_LENGTH, length);
}
vprev = v;
}
}
}
void BKE_editstrands_ensure(BMEditStrands *es)
{
BM_strands_cd_flag_ensure(es->base.bm, 0);
if (es->flag & BM_STRANDS_DIRTY_SEGLEN) {
editstrands_calc_segment_lengths(es->base.bm);
es->flag &= ~BM_STRANDS_DIRTY_SEGLEN;
}
}
/* === Particle Conversion === */
BMesh *BKE_editstrands_particles_to_bmesh(Object *ob, ParticleSystem *psys)
{
ParticleSystemModifierData *psmd = psys_get_modifier(ob, psys);
const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_PSYS(psys);
BMesh *bm;
bm = BM_mesh_create(&allocsize,
&((struct BMeshCreateParams){.use_toolflags = false,}));
if (psmd && psmd->dm_final) {
DM_ensure_tessface(psmd->dm_final);
BM_strands_bm_from_psys(bm, ob, psys, psmd->dm_final, true, /*psys->shapenr*/ -1);
editstrands_calc_segment_lengths(bm);
}
return bm;
}
void BKE_editstrands_particles_from_bmesh(Object *ob, ParticleSystem *psys)
{
ParticleSystemModifierData *psmd = psys_get_modifier(ob, psys);
BMesh *bm = psys->hairedit ? psys->hairedit->base.bm : NULL;
if (bm) {
if (psmd && psmd->dm_final) {
BVHTreeFromMesh bvhtree = {NULL};
DM_ensure_tessface(psmd->dm_final);
bvhtree_from_mesh_faces(&bvhtree, psmd->dm_final, 0.0, 2, 6);
BM_strands_bm_to_psys(bm, ob, psys, psmd->dm_final, &bvhtree);
free_bvhtree_from_mesh(&bvhtree);
}
}
}
/* === Mesh Conversion === */
BMesh *BKE_editstrands_mesh_to_bmesh(Object *ob, Mesh *me)
{
const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(me);
BMesh *bm;
struct BMeshFromMeshParams params = {0};
bm = BM_mesh_create(&allocsize,
&((struct BMeshCreateParams){.use_toolflags = false,}));
params.use_shapekey = true;
params.active_shapekey = ob->shapenr;
BM_mesh_bm_from_me(bm, me, &params);
BM_strands_cd_flag_ensure(bm, 0);
editstrands_calc_segment_lengths(bm);
return bm;
}
void BKE_editstrands_mesh_from_bmesh(Object *ob)
{
Mesh *me = ob->data;
BMesh *bm = me->edit_strands->base.bm;
struct BMeshToMeshParams params = {0};
/* Workaround for T42360, 'ob->shapenr' should be 1 in this case.
* however this isn't synchronized between objects at the moment. */
if (UNLIKELY((ob->shapenr == 0) && (me->key && !BLI_listbase_is_empty(&me->key->block)))) {
bm->shapenr = 1;
}
BM_mesh_bm_to_me(bm, me, &params);
#ifdef USE_TESSFACE_DEFAULT
BKE_mesh_tessface_calc(me);
#endif
/* free derived mesh. usually this would happen through depsgraph but there
* are exceptions like file save that will not cause this, and we want to
* avoid ending up with an invalid derived mesh then */
BKE_object_free_derived_caches(ob);
}
/* === Draw Cache === */
void (*BKE_editstrands_batch_cache_dirty_cb)(BMEditStrands *es, int mode) = NULL;
void (*BKE_editstrands_batch_cache_free_cb)(BMEditStrands *es) = NULL;
void BKE_editstrands_batch_cache_dirty(BMEditStrands *es, int mode)
{
if (es->batch_cache) {
BKE_editstrands_batch_cache_dirty_cb(es, mode);
}
}
void BKE_editstrands_batch_cache_free(BMEditStrands *es)
{
if (es->batch_cache) {
BKE_editstrands_batch_cache_free_cb(es);
}
}

View File

@@ -0,0 +1,455 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) Blender Foundation
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Lukas Toenne
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/blenkernel/intern/hair.c
* \ingroup bke
*/
#include <limits.h>
#include <string.h>
#include "MEM_guardedalloc.h"
#include "BLI_math.h"
#include "BLI_kdtree.h"
#include "BLI_listbase.h"
#include "BLI_rand.h"
#include "BLI_sort.h"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.h"
#include "DNA_hair_types.h"
#include "BKE_DerivedMesh.h"
#include "BKE_mesh_sample.h"
#include "BKE_hair.h"
#include "BLT_translation.h"
HairPattern* BKE_hair_new(void)
{
HairPattern *hair = MEM_callocN(sizeof(HairPattern), "hair");
/* add a default hair group */
BKE_hair_group_new(hair, HAIR_GROUP_TYPE_NORMALS);
return hair;
}
HairPattern* BKE_hair_copy(HairPattern *hair)
{
HairPattern *nhair = MEM_dupallocN(hair);
nhair->follicles = MEM_dupallocN(hair->follicles);
BLI_duplicatelist(&nhair->groups, &hair->groups);
return nhair;
}
static void hair_group_free(HairGroup *group)
{
BKE_hair_batch_cache_free(group);
if (group->strands_parent_index) {
MEM_freeN(group->strands_parent_index);
}
if (group->strands_parent_weight) {
MEM_freeN(group->strands_parent_weight);
}
}
void BKE_hair_free(struct HairPattern *hair)
{
if (hair->follicles) {
MEM_freeN(hair->follicles);
}
for (HairGroup *group = hair->groups.first; group; group = group->next) {
hair_group_free(group);
}
BLI_freelistN(&hair->groups);
MEM_freeN(hair);
}
void BKE_hair_set_num_follicles(HairPattern *hair, int count)
{
if (hair->num_follicles != count) {
if (count > 0) {
if (hair->follicles) {
hair->follicles = MEM_reallocN_id(hair->follicles, sizeof(HairFollicle) * count, "hair follicles");
}
else {
hair->follicles = MEM_callocN(sizeof(HairFollicle) * count, "hair follicles");
}
}
else {
if (hair->follicles) {
MEM_freeN(hair->follicles);
hair->follicles = NULL;
}
}
hair->num_follicles = count;
}
}
void BKE_hair_follicles_generate(HairPattern *hair, DerivedMesh *scalp, int count, unsigned int seed)
{
BKE_hair_set_num_follicles(hair, count);
if (count == 0) {
return;
}
MeshSampleGenerator *gen = BKE_mesh_sample_gen_surface_random(seed, true, NULL, NULL);
BKE_mesh_sample_generator_bind(gen, scalp);
unsigned int i;
HairFollicle *foll = hair->follicles;
for (i = 0; i < count; ++i, ++foll) {
bool ok = BKE_mesh_sample_generate(gen, &foll->mesh_sample);
if (!ok) {
/* clear remaining samples */
memset(foll, 0, sizeof(HairFollicle) * (count - i));
break;
}
}
BKE_mesh_sample_free_generator(gen);
BKE_hair_batch_cache_all_dirty(hair, BKE_HAIR_BATCH_DIRTY_ALL);
BKE_hair_update_groups(hair);
}
HairGroup* BKE_hair_group_new(HairPattern *hair, int type)
{
HairGroup *group = MEM_callocN(sizeof(HairGroup), "hair group");
group->type = type;
BKE_hair_group_name_set(hair, group, DATA_("Group"));
switch (type) {
case HAIR_GROUP_TYPE_NORMALS:
group->normals_max_length = 0.1f;
break;
case HAIR_GROUP_TYPE_STRANDS:
break;
}
BLI_addtail(&hair->groups, group);
return group;
}
void BKE_hair_group_remove(HairPattern *hair, HairGroup *group)
{
if (!group) {
return;
}
BLI_assert(BLI_findindex(&hair->groups, group) >= 0);
BLI_remlink(&hair->groups, group);
hair_group_free(group);
MEM_freeN(group);
}
HairGroup* BKE_hair_group_copy(HairPattern *hair, HairGroup *group)
{
if (!group) {
return NULL;
}
HairGroup *ngroup = MEM_dupallocN(group);
BLI_insertlinkafter(&hair->groups, group, ngroup);
return ngroup;
}
void BKE_hair_group_moveto(HairPattern *hair, HairGroup *group, int position)
{
if (!group) {
return;
}
BLI_assert(BLI_findindex(&hair->groups, group) >= 0);
BLI_remlink(&hair->groups, group);
BLI_insertlinkbefore(&hair->groups, BLI_findlink(&hair->groups, position), group);
}
void BKE_hair_group_name_set(HairPattern *hair, HairGroup *group, const char *name)
{
BLI_strncpy_utf8(group->name, name, sizeof(group->name));
BLI_uniquename(&hair->groups, group, DATA_("Group"), '.', offsetof(HairGroup, name), sizeof(group->name));
}
#define HAIR_FOLLICLE_GROUP_NONE INT_MAX
static void hair_claim_group_follicle(HairGroup *group, int group_index, int *follicle_group, int i)
{
if (follicle_group[i] == HAIR_FOLLICLE_GROUP_NONE) {
follicle_group[i] = group_index;
++group->num_follicles;
}
}
static void hair_group_follicles_normals(HairPattern *hair, HairGroup *group, int group_index, int *follicle_group)
{
const int num_follicles = hair->num_follicles;
for (int i = 0; i < num_follicles; ++i) {
// claim all
hair_claim_group_follicle(group, group_index, follicle_group, i);
}
}
static void hair_group_follicles_strands(HairPattern *hair, HairGroup *group, int group_index, int *follicle_group)
{
// TODO
UNUSED_VARS(hair, group, group_index, follicle_group);
}
typedef struct HairFollicleSortContext {
const HairFollicle *start;
const int *follicle_group;
} HairFollicleSortContext;
static int cmpHairFollicleByGroup(const void *a, const void *b, void *ctx_)
{
const HairFollicleSortContext *ctx = (const HairFollicleSortContext *)ctx_;
const size_t ia = (const HairFollicle *)a - ctx->start;
const size_t ib = (const HairFollicle *)b - ctx->start;
return ctx->follicle_group[ib] - ctx->follicle_group[ia];
}
void BKE_hair_update_groups(HairPattern *hair)
{
const int num_follicles = hair->num_follicles;
int *follicle_group = MEM_mallocN(sizeof(int) * num_follicles, "hair follicle group index");
for (int i = 0; i < num_follicles; ++i) {
follicle_group[i] = HAIR_FOLLICLE_GROUP_NONE;
}
int group_index = 0;
for (HairGroup *group = hair->groups.first; group; group = group->next, ++group_index) {
// Note: follicles array is sorted below
if (group->prev) {
group->follicles = group->prev->follicles + group->prev->num_follicles;
}
else {
group->follicles = hair->follicles;
}
group->num_follicles = 0;
switch (group->type) {
case HAIR_GROUP_TYPE_NORMALS:
hair_group_follicles_normals(hair, group, group_index, follicle_group);
break;
case HAIR_GROUP_TYPE_STRANDS:
hair_group_follicles_strands(hair, group, group_index, follicle_group);
break;
}
}
{
HairFollicleSortContext ctx;
ctx.start = hair->follicles;
ctx.follicle_group = follicle_group;
BLI_qsort_r(hair->follicles, num_follicles, sizeof(HairFollicle), cmpHairFollicleByGroup, &ctx);
}
MEM_freeN(follicle_group);
BKE_hair_batch_cache_all_dirty(hair, BKE_HAIR_BATCH_DIRTY_ALL);
}
/* ================================= */
typedef struct HairGroupDrawDataInterface {
HairDrawDataInterface base;
int numstrands;
int numverts_orig;
} HairGroupDrawDataInterface;
static int get_num_strands(const HairDrawDataInterface *hairdata_)
{
const HairGroupDrawDataInterface *hairdata = (HairGroupDrawDataInterface *)hairdata_;
return hairdata->numstrands;
}
static int get_num_verts(const HairDrawDataInterface *hairdata_)
{
const HairGroupDrawDataInterface *hairdata = (HairGroupDrawDataInterface *)hairdata_;
return hairdata->numverts_orig;
}
static void get_strand_lengths_normals(const HairDrawDataInterface* hairdata_, int *r_lengths)
{
const HairGroupDrawDataInterface *hairdata = (HairGroupDrawDataInterface *)hairdata_;
for (int i = 0; i < hairdata->numstrands; ++i)
{
r_lengths[i] = 2;
}
}
static void get_strand_roots_normals(const HairDrawDataInterface* hairdata_, struct MeshSample *r_roots)
{
const HairGroupDrawDataInterface *hairdata = (HairGroupDrawDataInterface *)hairdata_;
DerivedMesh *scalp = hairdata->base.scalp;
MeshSampleGenerator *gen = BKE_mesh_sample_gen_surface_vertices();
BKE_mesh_sample_generator_bind(gen, scalp);
int i = 0;
for (; i < hairdata->numstrands; ++i)
{
if (!BKE_mesh_sample_generate(gen, &r_roots[i])) {
break;
}
}
// clear remaining samples, if any
for (; i < hairdata->numstrands; ++i)
{
BKE_mesh_sample_clear(&r_roots[i]);
}
BKE_mesh_sample_free_generator(gen);
}
static void get_strand_vertices_normals(const HairDrawDataInterface* hairdata_, float (*r_verts)[3])
{
const HairGroupDrawDataInterface *hairdata = (HairGroupDrawDataInterface *)hairdata_;
DerivedMesh *scalp = hairdata->base.scalp;
MeshSampleGenerator *gen = BKE_mesh_sample_gen_surface_vertices();
BKE_mesh_sample_generator_bind(gen, scalp);
int i = 0;
for (; i < hairdata->numstrands; ++i)
{
MeshSample sample;
if (!BKE_mesh_sample_generate(gen, &sample)) {
break;
}
float co[3], nor[3], tang[3];
BKE_mesh_sample_eval(scalp, &sample, co, nor, tang);
copy_v3_v3(r_verts[i << 1], co);
madd_v3_v3v3fl(r_verts[(i << 1) + 1], co, nor, hairdata->base.group->normals_max_length);
}
// clear remaining data, if any
for (; i < hairdata->numstrands; ++i)
{
zero_v3(r_verts[i << 1]);
zero_v3(r_verts[(i << 1) + 1]);
}
BKE_mesh_sample_free_generator(gen);
}
static void get_strand_lengths_strands(const HairDrawDataInterface* hairdata_, int *r_lengths)
{
const HairGroupDrawDataInterface *hairdata = (HairGroupDrawDataInterface *)hairdata_;
for (int i = 0; i < hairdata->numstrands; ++i)
{
// TODO
r_lengths[i] = 0;
}
}
static void get_strand_roots_strands(const HairDrawDataInterface* hairdata_, struct MeshSample *r_roots)
{
const HairGroupDrawDataInterface *hairdata = (HairGroupDrawDataInterface *)hairdata_;
for (int i = 0; i < hairdata->numstrands; ++i)
{
// TODO
memset(&r_roots[i], 0, sizeof(MeshSample));
}
}
static void get_strand_vertices_strands(const HairDrawDataInterface* hairdata_, float (*r_verts)[3])
{
const HairGroupDrawDataInterface *hairdata = (HairGroupDrawDataInterface *)hairdata_;
for (int i = 0; i < hairdata->numverts_orig; ++i)
{
// TODO
zero_v3(r_verts[i]);
}
}
static HairGroupDrawDataInterface hair_group_get_interface(HairGroup *group, DerivedMesh *scalp)
{
HairGroupDrawDataInterface hairdata;
hairdata.base.group = group;
hairdata.base.scalp = scalp;
hairdata.base.get_num_strands = get_num_strands;
hairdata.base.get_num_verts = get_num_verts;
switch (group->type) {
case HAIR_GROUP_TYPE_NORMALS: {
hairdata.numstrands = scalp->getNumVerts(scalp);
hairdata.numverts_orig = 2 * hairdata.numstrands;
hairdata.base.get_strand_lengths = get_strand_lengths_normals;
hairdata.base.get_strand_roots = get_strand_roots_normals;
hairdata.base.get_strand_vertices = get_strand_vertices_normals;
break;
}
case HAIR_GROUP_TYPE_STRANDS: {
// TODO
hairdata.numstrands = 0;
hairdata.numverts_orig = 0;
hairdata.base.get_strand_lengths = get_strand_lengths_strands;
hairdata.base.get_strand_roots = get_strand_roots_strands;
hairdata.base.get_strand_vertices = get_strand_vertices_strands;
break;
}
}
return hairdata;
}
int* BKE_hair_group_get_fiber_lengths(HairGroup *group, DerivedMesh *scalp, int subdiv)
{
HairGroupDrawDataInterface hairdata = hair_group_get_interface(group, scalp);
return BKE_hair_strands_get_fiber_lengths(&hairdata.base, subdiv);
}
void BKE_hair_group_get_texture_buffer_size(HairGroup *group, DerivedMesh *scalp, int subdiv,
int *r_size, int *r_strand_map_start,
int *r_strand_vertex_start, int *r_fiber_start)
{
HairGroupDrawDataInterface hairdata = hair_group_get_interface(group, scalp);
BKE_hair_strands_get_texture_buffer_size(&hairdata.base, subdiv,
r_size, r_strand_map_start, r_strand_vertex_start, r_fiber_start);
}
void BKE_hair_group_get_texture_buffer(HairGroup *group, DerivedMesh *scalp, int subdiv, void *buffer)
{
HairGroupDrawDataInterface hairdata = hair_group_get_interface(group, scalp);
BKE_hair_strands_get_texture_buffer(&hairdata.base, subdiv, buffer);
}

View File

@@ -0,0 +1,606 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) Blender Foundation
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Lukas Toenne
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/blenkernel/intern/hair_draw.c
* \ingroup bke
*/
#include <string.h>
#include "MEM_guardedalloc.h"
#include "BLI_math.h"
#include "BLI_kdtree.h"
#include "BLI_rand.h"
#include "DNA_hair_types.h"
#include "BKE_DerivedMesh.h"
#include "BKE_mesh_sample.h"
#include "BKE_hair.h"
#if 0
bool BKE_hair_fiber_get_location(const HairFiber *fiber, DerivedMesh *root_dm, float loc[3])
{
float nor[3], tang[3];
if (BKE_mesh_sample_eval(root_dm, &fiber->root, loc, nor, tang)) {
return true;
}
else {
zero_v3(loc);
return false;
}
}
bool BKE_hair_fiber_get_vectors(const HairFiber *fiber, DerivedMesh *root_dm,
float loc[3], float nor[3], float tang[3])
{
if (BKE_mesh_sample_eval(root_dm, &fiber->root, loc, nor, tang)) {
return true;
}
else {
zero_v3(loc);
zero_v3(nor);
zero_v3(tang);
return false;
}
}
bool BKE_hair_fiber_get_matrix(const HairFiber *fiber, DerivedMesh *root_dm, float mat[4][4])
{
if (BKE_mesh_sample_eval(root_dm, &fiber->root, mat[3], mat[2], mat[0])) {
cross_v3_v3v3(mat[1], mat[2], mat[0]);
mat[0][3] = 0.0f;
mat[1][3] = 0.0f;
mat[2][3] = 0.0f;
mat[3][3] = 1.0f;
return true;
}
else {
unit_m4(mat);
return false;
}
}
BLI_INLINE void verify_fiber_weights(HairFiber *fiber)
{
const float *w = fiber->parent_weight;
BLI_assert(w[0] >= 0.0f && w[1] >= 0.0f && w[2] >= 0.0f && w[3] >= 0.0f);
float sum = w[0] + w[1] + w[2] + w[3];
float epsilon = 1.0e-2;
BLI_assert(sum > 1.0f - epsilon && sum < 1.0f + epsilon);
UNUSED_VARS(sum, epsilon);
BLI_assert(w[0] >= w[1] && w[1] >= w[2] && w[2] >= w[3]);
}
static void sort_fiber_weights(HairFiber *fiber)
{
unsigned int *idx = fiber->parent_index;
float *w = fiber->parent_weight;
#define FIBERSWAP(a, b) \
SWAP(unsigned int, idx[a], idx[b]); \
SWAP(float, w[a], w[b]);
for (int k = 0; k < 3; ++k) {
int maxi = k;
float maxw = w[k];
for (int i = k+1; i < 4; ++i) {
if (w[i] > maxw) {
maxi = i;
maxw = w[i];
}
}
if (maxi != k)
FIBERSWAP(k, maxi);
}
#undef FIBERSWAP
}
static void strand_find_closest(HairFiber *fiber, const float loc[3],
const KDTree *tree, const float (*strandloc)[3])
{
/* Use the 3 closest strands for interpolation.
* Note that we have up to 4 possible weights, but we
* only look for a triangle with this method.
*/
KDTreeNearest nearest[3];
const float *sloc[3] = {NULL};
int k, found = BLI_kdtree_find_nearest_n(tree, loc, nearest, 3);
for (k = 0; k < found; ++k) {
fiber->parent_index[k] = nearest[k].index;
sloc[k] = strandloc[nearest[k].index];
}
for (; k < 4; ++k) {
fiber->parent_index[k] = STRAND_INDEX_NONE;
fiber->parent_weight[k] = 0.0f;
}
/* calculate barycentric interpolation weights */
if (found == 3) {
float closest[3];
closest_on_tri_to_point_v3(closest, loc, sloc[0], sloc[1], sloc[2]);
float w[3];
interp_weights_tri_v3(w, sloc[0], sloc[1], sloc[2], closest);
copy_v3_v3(fiber->parent_weight, w);
/* float precisions issues can cause slightly negative weights */
CLAMP3(fiber->parent_weight, 0.0f, 1.0f);
}
else if (found == 2) {
fiber->parent_weight[1] = line_point_factor_v3(loc, sloc[0], sloc[1]);
fiber->parent_weight[0] = 1.0f - fiber->parent_weight[1];
/* float precisions issues can cause slightly negative weights */
CLAMP2(fiber->parent_weight, 0.0f, 1.0f);
}
else if (found == 1) {
fiber->parent_weight[0] = 1.0f;
}
sort_fiber_weights(fiber);
}
static void strand_calc_root_distance(HairFiber *fiber, const float loc[3], const float nor[3], const float tang[3],
const float (*strandloc)[3])
{
if (fiber->parent_index[0] == STRAND_INDEX_NONE)
return;
float cotang[3];
cross_v3_v3v3(cotang, nor, tang);
const float *sloc0 = strandloc[fiber->parent_index[0]];
float dist[3];
sub_v3_v3v3(dist, loc, sloc0);
fiber->root_distance[0] = dot_v3v3(dist, tang);
fiber->root_distance[1] = dot_v3v3(dist, cotang);
}
static void strands_calc_weights(const HairDrawDataInterface *hairdata, struct DerivedMesh *scalp, HairFiber *fibers, int num_fibers)
{
const int num_strands = hairdata->get_num_strands(hairdata);
if (num_strands == 0)
return;
float (*strandloc)[3] = MEM_mallocN(sizeof(float) * 3 * num_strands, "strand locations");
{
MeshSample *roots = MEM_mallocN(sizeof(MeshSample) * num_strands, "strand roots");
hairdata->get_strand_roots(hairdata, roots);
for (int i = 0; i < num_strands; ++i) {
float nor[3], tang[3];
if (!BKE_mesh_sample_eval(scalp, &roots[i], strandloc[i], nor, tang)) {
zero_v3(strandloc[i]);
}
}
MEM_freeN(roots);
}
KDTree *tree = BLI_kdtree_new(num_strands);
for (int c = 0; c < num_strands; ++c) {
BLI_kdtree_insert(tree, c, strandloc[c]);
}
BLI_kdtree_balance(tree);
HairFiber *fiber = fibers;
for (int i = 0; i < num_fibers; ++i, ++fiber) {
float loc[3], nor[3], tang[3];
if (BKE_mesh_sample_eval(scalp, &fiber->root, loc, nor, tang)) {
strand_find_closest(fiber, loc, tree, strandloc);
verify_fiber_weights(fiber);
strand_calc_root_distance(fiber, loc, nor, tang, strandloc);
}
}
BLI_kdtree_free(tree);
MEM_freeN(strandloc);
}
HairFiber* BKE_hair_fibers_create(const HairDrawDataInterface *hairdata,
struct DerivedMesh *scalp, unsigned int amount,
unsigned int seed)
{
MeshSampleGenerator *gen = BKE_mesh_sample_gen_surface_random(scalp, seed);
unsigned int i;
HairFiber *fibers = MEM_mallocN(sizeof(HairFiber) * amount, "HairFiber");
HairFiber *fiber;
for (i = 0, fiber = fibers; i < amount; ++i, ++fiber) {
if (BKE_mesh_sample_generate(gen, &fiber->root)) {
int k;
/* influencing control strands are determined later */
for (k = 0; k < 4; ++k) {
fiber->parent_index[k] = STRAND_INDEX_NONE;
fiber->parent_weight[k] = 0.0f;
}
}
else {
/* clear remaining samples */
memset(fiber, 0, sizeof(HairFiber) * (amount - i));
break;
}
}
BKE_mesh_sample_free_generator(gen);
strands_calc_weights(hairdata, scalp, fibers, amount);
return fibers;
}
#endif
static int hair_get_strand_subdiv_numverts(int numstrands, int numverts, int subdiv)
{
return ((numverts - numstrands) << subdiv) + numstrands;
}
BLI_INLINE int hair_get_strand_subdiv_length(int orig_length, int subdiv)
{
return ((orig_length - 1) << subdiv) + 1;
}
static void hair_get_strand_subdiv_lengths(int *lengths, const int *orig_lengths, int num_strands, int subdiv)
{
for (int i = 0; i < num_strands; ++i) {
lengths[i] = hair_get_strand_subdiv_length(orig_lengths[i], subdiv);
}
}
int* BKE_hair_strands_get_fiber_lengths(const HairDrawDataInterface *hairdata, int subdiv)
{
if (!hairdata->group) {
return NULL;
}
const int totfibers = hairdata->group->num_follicles;
int *fiber_length = MEM_mallocN(sizeof(int) * totfibers, "fiber length");
switch (hairdata->group->type) {
case HAIR_GROUP_TYPE_NORMALS: {
const int length = hair_get_strand_subdiv_length(2, subdiv);
for (int i = 0; i < totfibers; ++i) {
fiber_length[i] = length;
}
break;
}
case HAIR_GROUP_TYPE_STRANDS: {
const int num_strands = hairdata->get_num_strands(hairdata);
int *lengths = MEM_mallocN(sizeof(int) * num_strands, "strand length");
hairdata->get_strand_lengths(hairdata, lengths);
hair_get_strand_subdiv_lengths(lengths, lengths, num_strands, subdiv);
for (int i = 0; i < totfibers; ++i) {
// Calculate the length of the fiber from the weighted average of its control strands
float fiblen = 0.0f;
const int *parent_index = hairdata->group->strands_parent_index[i];
const float *parent_weight = hairdata->group->strands_parent_weight[i];
for (int k = 0; k < 4; ++k) {
int si = parent_index[k];
float sw = parent_weight[k];
if (si == STRAND_INDEX_NONE || sw == 0.0f) {
break;
}
BLI_assert(si < num_strands);
fiblen += (float)lengths[si] * sw;
}
// use rounded number of segments
fiber_length[i] = (int)(fiblen + 0.5f);
}
MEM_freeN(lengths);
break;
}
}
return fiber_length;
}
typedef struct HairFiberTextureBuffer {
unsigned int parent_index[4];
float parent_weight[4];
float root_position[3];
int pad;
} HairFiberTextureBuffer;
BLI_STATIC_ASSERT_ALIGN(HairFiberTextureBuffer, 8)
typedef struct HairStrandVertexTextureBuffer {
float co[3];
float nor[3];
float tang[3];
int pad;
} HairStrandVertexTextureBuffer;
BLI_STATIC_ASSERT_ALIGN(HairStrandVertexTextureBuffer, 8)
typedef struct HairStrandMapTextureBuffer {
unsigned int vertex_start;
unsigned int vertex_count;
} HairStrandMapTextureBuffer;
BLI_STATIC_ASSERT_ALIGN(HairStrandMapTextureBuffer, 8)
static void hair_get_texture_buffer_size(int numstrands, int numverts_orig, int subdiv, int numfibers,
int *r_size, int *r_strand_map_start,
int *r_strand_vertex_start, int *r_fiber_start)
{
const int numverts = hair_get_strand_subdiv_numverts(numstrands, numverts_orig, subdiv);
*r_strand_map_start = 0;
*r_strand_vertex_start = *r_strand_map_start + numstrands * sizeof(HairStrandMapTextureBuffer);
*r_fiber_start = *r_strand_vertex_start + numverts * sizeof(HairStrandVertexTextureBuffer);
*r_size = *r_fiber_start + numfibers * sizeof(HairFiberTextureBuffer);
}
static void hair_strand_transport_frame(const float co1[3], const float co2[3],
float prev_tang[3], float prev_nor[3],
float r_tang[3], float r_nor[3])
{
/* segment direction */
sub_v3_v3v3(r_tang, co2, co1);
normalize_v3(r_tang);
/* rotate the frame */
float rot[3][3];
rotation_between_vecs_to_mat3(rot, prev_tang, r_tang);
mul_v3_m3v3(r_nor, rot, prev_nor);
copy_v3_v3(prev_tang, r_tang);
copy_v3_v3(prev_nor, r_nor);
}
static void hair_strand_calc_vectors(const float (*positions)[3], int num_verts, float rootmat[3][3],
HairStrandVertexTextureBuffer *strand)
{
for (int i = 0; i < num_verts; ++i) {
copy_v3_v3(strand[i].co, positions[i]);
}
// Calculate tangent and normal vectors
{
BLI_assert(num_verts >= 2);
float prev_tang[3], prev_nor[3];
copy_v3_v3(prev_tang, rootmat[2]);
copy_v3_v3(prev_nor, rootmat[0]);
hair_strand_transport_frame(strand[0].co, strand[1].co,
prev_tang, prev_nor,
strand[0].tang, strand[0].nor);
for (int i = 1; i < num_verts - 1; ++i) {
hair_strand_transport_frame(strand[i-1].co, strand[i+1].co,
prev_tang, prev_nor,
strand[i].tang, strand[i].nor);
}
hair_strand_transport_frame(strand[num_verts-2].co, strand[num_verts-1].co,
prev_tang, prev_nor,
strand[num_verts-1].tang, strand[num_verts-1].nor);
}
}
static int hair_strand_subdivide(float (*verts)[3], const float (*verts_orig)[3], int numverts_orig, int subdiv)
{
{
/* Move vertex positions from the dense array to their initial configuration for subdivision. */
const int step = (1 << subdiv);
const float (*src)[3] = verts_orig;
float (*dst)[3] = verts;
for (int i = 0; i < numverts_orig; ++i) {
copy_v3_v3(*dst, *src);
++src;
dst += step;
}
}
/* Subdivide */
for (int d = 0; d < subdiv; ++d) {
const int num_edges = (numverts_orig - 1) << d;
const int hstep = 1 << (subdiv - d - 1);
const int step = 1 << (subdiv - d);
/* Calculate edge points */
{
int index = 0;
for (int k = 0; k < num_edges; ++k, index += step) {
add_v3_v3v3(verts[index + hstep], verts[index], verts[index + step]);
mul_v3_fl(verts[index + hstep], 0.5f);
}
}
/* Move original points */
{
int index = step;
for (int k = 1; k < num_edges; ++k, index += step) {
add_v3_v3v3(verts[index], verts[index - hstep], verts[index + hstep]);
mul_v3_fl(verts[index], 0.5f);
}
}
}
const int num_verts = ((numverts_orig - 1) << subdiv) + 1;
return num_verts;
}
static void hair_get_strand_buffer(DerivedMesh *scalp, int numstrands, int numverts_orig, int subdiv,
const int *lengths_orig, const float (*vertco_orig)[3], const MeshSample *roots,
HairStrandMapTextureBuffer *strand_map_buffer,
HairStrandVertexTextureBuffer *strand_vertex_buffer)
{
const int numverts = hair_get_strand_subdiv_numverts(numstrands, numverts_orig, subdiv);
const int *lengths;
const float (*vertco)[3];
int *lengths_subdiv = NULL;
float (*vertco_subdiv)[3] = NULL;
if (subdiv > 0) {
lengths = lengths_subdiv = MEM_mallocN(sizeof(int) * numstrands, "strand lengths subdivided");
hair_get_strand_subdiv_lengths(lengths_subdiv, lengths_orig, numstrands, subdiv);
vertco = vertco_subdiv = MEM_mallocN(sizeof(float[3]) * numverts, "strand vertex positions subdivided");
}
else {
lengths = lengths_orig;
vertco = vertco_orig;
}
HairStrandMapTextureBuffer *smap = strand_map_buffer;
HairStrandVertexTextureBuffer *svert = strand_vertex_buffer;
int vertex_orig_start = 0;
int vertex_start = 0;
for (int i = 0; i < numstrands; ++i) {
const int len_orig = lengths_orig[i];
const int len = lengths[i];
smap->vertex_start = vertex_start;
smap->vertex_count = len;
if (subdiv > 0) {
hair_strand_subdivide(vertco_subdiv + vertex_start, vertco_orig + vertex_orig_start, len_orig, subdiv);
}
{
float pos[3];
float matrix[3][3];
BKE_mesh_sample_eval(scalp, &roots[i], pos, matrix[2], matrix[0]);
cross_v3_v3v3(matrix[1], matrix[2], matrix[0]);
hair_strand_calc_vectors(vertco + vertex_start, len, matrix, svert);
}
vertex_orig_start += len_orig;
vertex_start += len;
++smap;
svert += len;
}
if (subdiv > 0) {
MEM_freeN(lengths_subdiv);
MEM_freeN(vertco_subdiv);
}
}
static void hair_get_fiber_buffer(const HairGroup *group, DerivedMesh *scalp,
HairFiberTextureBuffer *fiber_buf)
{
const int totfibers = group->num_follicles;
HairFiberTextureBuffer *fb = fiber_buf;
float nor[3], tang[3];
switch (group->type) {
case HAIR_GROUP_TYPE_NORMALS: {
HairFollicle *foll = group->follicles;
for (int i = 0; i < totfibers; ++i, ++fb, ++foll) {
for (int k = 0; k < 3; ++k) {
fb->parent_index[k] = foll->mesh_sample.orig_verts[k];
fb->parent_weight[k] = foll->mesh_sample.orig_weights[k];
}
fb->parent_index[3] = STRAND_INDEX_NONE;
fb->parent_weight[3] = 0.0f;
BKE_mesh_sample_eval(scalp, &group->follicles[i].mesh_sample, fb->root_position, nor, tang);
}
break;
}
case HAIR_GROUP_TYPE_STRANDS: {
BLI_assert(group->strands_parent_index != NULL);
BLI_assert(group->strands_parent_weight != NULL);
HairFollicle *foll = group->follicles;
for (int i = 0; i < totfibers; ++i, ++fb, ++foll) {
memcpy(fb->parent_index, group->strands_parent_index[i], sizeof(fb->parent_index));
memcpy(fb->parent_weight, group->strands_parent_weight[i], sizeof(fb->parent_weight));
BKE_mesh_sample_eval(scalp, &foll->mesh_sample, fb->root_position, nor, tang);
}
break;
}
}
}
void BKE_hair_strands_get_texture_buffer_size(const HairDrawDataInterface *hairdata, int subdiv,
int *r_size, int *r_strand_map_start,
int *r_strand_vertex_start, int *r_fiber_start)
{
const int totstrands = hairdata->get_num_strands(hairdata);
const int totverts = hairdata->get_num_verts(hairdata);
hair_get_texture_buffer_size(totstrands, totverts, subdiv, hairdata->group->num_follicles,
r_size, r_strand_map_start, r_strand_vertex_start, r_fiber_start);
}
void BKE_hair_strands_get_texture_buffer(const HairDrawDataInterface *hairdata, int subdiv, void *buffer)
{
const int totstrands = hairdata->get_num_strands(hairdata);
const int totverts_orig = hairdata->get_num_verts(hairdata);
int size, strand_map_start, strand_vertex_start, fiber_start;
hair_get_texture_buffer_size(totstrands, totverts_orig, subdiv, hairdata->group->num_follicles,
&size, &strand_map_start, &strand_vertex_start, &fiber_start);
int *lengths_orig = MEM_mallocN(sizeof(int) * totstrands, "strand lengths");
float (*vertco_orig)[3] = MEM_mallocN(sizeof(float[3]) * totverts_orig, "strand vertex positions");
MeshSample *roots = MEM_mallocN(sizeof(MeshSample) * totstrands, "strand roots");
hairdata->get_strand_lengths(hairdata, lengths_orig);
hairdata->get_strand_vertices(hairdata, vertco_orig);
hairdata->get_strand_roots(hairdata, roots);
hair_get_strand_buffer(hairdata->scalp, totstrands, totverts_orig, subdiv,
lengths_orig, vertco_orig, roots,
(HairStrandMapTextureBuffer*)((char*)buffer + strand_map_start),
(HairStrandVertexTextureBuffer*)((char*)buffer + strand_vertex_start));
hair_get_fiber_buffer(hairdata->group, hairdata->scalp, (HairFiberTextureBuffer*)((char*)buffer + fiber_start));
MEM_freeN(lengths_orig);
MEM_freeN(vertco_orig);
MEM_freeN(roots);
}
void (*BKE_hair_batch_cache_dirty_cb)(HairGroup *group, int mode) = NULL;
void (*BKE_hair_batch_cache_free_cb)(HairGroup *group) = NULL;
void BKE_hair_batch_cache_dirty(HairGroup *group, int mode)
{
if (group->draw_batch_cache) {
BKE_hair_batch_cache_dirty_cb(group, mode);
}
}
void BKE_hair_batch_cache_all_dirty(struct HairPattern *hair, int mode)
{
for (HairGroup *group = hair->groups.first; group; group = group->next) {
BKE_hair_batch_cache_dirty(group, mode);
}
}
void BKE_hair_batch_cache_free(HairGroup *group)
{
if (group->draw_batch_cache || group->draw_texture_cache) {
BKE_hair_batch_cache_free_cb(group);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -977,6 +977,7 @@ ParticleSystem *BKE_object_copy_particlesystem(ParticleSystem *psys, const int f
psysn->pathcache = NULL;
psysn->childcache = NULL;
psysn->edit = NULL;
psysn->hairedit = NULL;
psysn->pdd = NULL;
psysn->effectors = NULL;
psysn->tree = NULL;

View File

@@ -65,6 +65,7 @@
#include "BKE_boids.h"
#include "BKE_cloth.h"
#include "BKE_colortools.h"
#include "BKE_editstrands.h"
#include "BKE_effect.h"
#include "BKE_global.h"
#include "BKE_group.h"
@@ -568,6 +569,11 @@ void psys_free(Object *ob, ParticleSystem *psys)
if (psys->edit && psys->free_edit)
psys->free_edit(psys->edit);
if (psys->hairedit) {
BKE_editstrands_free(psys->hairedit);
MEM_freeN(psys->hairedit);
psys->hairedit = NULL;
}
if (psys->child) {
MEM_freeN(psys->child);
@@ -1606,6 +1612,11 @@ static int psys_map_index_on_dm(DerivedMesh *dm, int from, int index, int index_
return 1;
}
int psys_get_index_on_dm(ParticleSystem *psys, DerivedMesh *dm, ParticleData *pa, int *mapindex, float mapfw[4])
{
return psys_map_index_on_dm(dm, psys->part->from, pa->num, pa->num_dmcache, pa->fuv, pa->foffset, mapindex, mapfw);
}
/* interprets particle data to get a point on a mesh in object space */
void psys_particle_on_dm(DerivedMesh *dm_final, int from, int index, int index_dmcache,
const float fw[4], float foffset, float vec[3], float nor[3], float utan[3], float vtan[3],
@@ -3207,7 +3218,7 @@ void object_remove_particle_system(Scene *UNUSED(scene), Object *ob)
if (ob->particlesystem.first)
((ParticleSystem *) ob->particlesystem.first)->flag |= PSYS_CURRENT;
else
ob->mode &= ~OB_MODE_PARTICLE_EDIT;
ob->mode &= ~(OB_MODE_PARTICLE_EDIT | OB_MODE_HAIR_EDIT);
DEG_relations_tag_update(G.main);
DEG_id_tag_update(&ob->id, OB_RECALC_DATA);

View File

@@ -913,6 +913,11 @@ void BKE_scene_init(Scene *sce)
sce->toolsettings->imapaint.normal_angle = 80;
sce->toolsettings->imapaint.seam_bleed = 2;
sce->toolsettings->hair_edit.select_mode = HAIR_SELECT_VERTEX;
sce->toolsettings->hair_edit.hair_draw_mode = HAIR_DRAW_FIBERS;
sce->toolsettings->hair_edit.hair_draw_size = 2.5f;
sce->toolsettings->hair_edit.hair_draw_subdiv = 2;
sce->physics_settings.gravity[0] = 0.0f;
sce->physics_settings.gravity[1] = 0.0f;
sce->physics_settings.gravity[2] = -9.81f;

View File

@@ -318,6 +318,8 @@ bool clip_segment_v3_plane_n(
/****************************** Interpolation ********************************/
void interp_weights_tri_v3(float w[3], const float a[3], const float b[3], const float c[3], const float p[3]);
void interp_weights_quad_v3(float w[4], const float a[3], const float b[3], const float c[3], const float d[3], const float p[3]);
/* also returns three indices of the triangle actually used */
void interp_weights_quad_v3_index(int tri[3], float w[4], const float v1[3], const float v2[3], const float v3[3], const float v4[3], const float co[3]);
void interp_weights_poly_v3(float w[], float v[][3], const int n, const float co[3]);
void interp_weights_poly_v2(float w[], float v[][2], const int n, const float co[2]);

View File

@@ -2830,6 +2830,71 @@ void interp_weights_quad_v3(float w[4], const float v1[3], const float v2[3], co
}
}
void interp_weights_quad_v3_index(int tri[3], float w[4], const float v1[3], const float v2[3], const float v3[3], const float v4[3], const float co[3])
{
float w2[3];
w[0] = w[1] = w[2] = w[3] = 0.0f;
tri[0] = tri[1] = tri[2] = -1;
/* first check for exact match */
if (equals_v3v3(co, v1)) {
w[0] = 1.0f;
tri[0] = 0; tri[1] = 1; tri[2] = 3;
}
else if (equals_v3v3(co, v2)) {
w[1] = 1.0f;
tri[0] = 0; tri[1] = 1; tri[2] = 3;
}
else if (equals_v3v3(co, v3)) {
w[2] = 1.0f;
tri[0] = 1; tri[1] = 2; tri[2] = 3;
}
else if (v4 && equals_v3v3(co, v4)) {
w[3] = 1.0f;
tri[0] = 1; tri[1] = 2; tri[2] = 3;
}
else {
/* otherwise compute barycentric interpolation weights */
float n1[3], n2[3], n[3];
bool degenerate;
sub_v3_v3v3(n1, v1, v3);
if (v4) {
sub_v3_v3v3(n2, v2, v4);
}
else {
sub_v3_v3v3(n2, v2, v3);
}
cross_v3_v3v3(n, n1, n2);
/* OpenGL seems to split this way, so we do too */
if (v4) {
degenerate = barycentric_weights(v1, v2, v4, co, n, w);
SWAP(float, w[2], w[3]);
tri[0] = 0; tri[1] = 1; tri[2] = 3;
if (degenerate || (w[0] < 0.0f)) {
/* if w[1] is negative, co is on the other side of the v1-v3 edge,
* so we interpolate using the other triangle */
degenerate = barycentric_weights(v2, v3, v4, co, n, w2);
if (!degenerate) {
w[0] = 0.0f;
w[1] = w2[0];
w[2] = w2[1];
w[3] = w2[2];
tri[0] = 1; tri[1] = 2; tri[2] = 3;
}
}
}
else {
barycentric_weights(v1, v2, v3, co, n, w);
tri[0] = 0; tri[1] = 1; tri[2] = 2;
}
}
}
/* return 1 of point is inside triangle, 2 if it's on the edge, 0 if point is outside of triangle */
int barycentric_inside_triangle_v2(const float w[3])
{

View File

@@ -73,6 +73,7 @@
#include "DNA_genfile.h"
#include "DNA_group_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_hair_types.h"
#include "DNA_ipo_types.h"
#include "DNA_key_types.h"
#include "DNA_lattice_types.h"
@@ -4423,6 +4424,7 @@ static void direct_link_particlesystems(FileData *fd, ListBase *particles)
psys->edit = NULL;
psys->free_edit = NULL;
psys->hairedit = NULL;
psys->pathcache = NULL;
psys->childcache = NULL;
BLI_listbase_clear(&psys->pathcachebufs);
@@ -4665,6 +4667,7 @@ static void direct_link_mesh(FileData *fd, Mesh *mesh)
mesh->bb = NULL;
mesh->edit_btmesh = NULL;
mesh->edit_strands = NULL;
mesh->batch_cache = NULL;
/* happens with old files */
@@ -5117,6 +5120,28 @@ static void direct_link_pose(FileData *fd, bPose *pose)
}
}
static void direct_link_hair(FileData *fd, HairPattern *hair)
{
if (!hair) {
return;
}
// cache the old pointer to calculate offsets for groups
const HairFollicle *old_follicles = hair->follicles;
hair->follicles = newdataadr(fd, hair->follicles);
link_list(fd, &hair->groups);
for (HairGroup *group = hair->groups.first; group; group = group->next) {
group->follicles = hair->follicles + (int)(group->follicles - old_follicles);
group->strands_parent_index = newdataadr(fd, group->strands_parent_index);
group->strands_parent_weight = newdataadr(fd, group->strands_parent_weight);
group->draw_batch_cache = NULL;
group->draw_texture_cache = NULL;
}
}
static void direct_link_modifiers(FileData *fd, ListBase *lb)
{
ModifierData *md;
@@ -5438,6 +5463,14 @@ static void direct_link_modifiers(FileData *fd, ListBase *lb)
}
}
}
else if (md->type == eModifierType_Hair) {
HairModifierData *hmd = (HairModifierData *)md;
hmd->hair = newdataadr(fd, hmd->hair);
direct_link_hair(fd, hmd->hair);
hmd->edit = NULL;
}
}
}
@@ -5467,7 +5500,7 @@ static void direct_link_object(FileData *fd, Object *ob)
* See [#34776, #42780] for more information.
*/
if (fd->memfile || (ob->id.tag & (LIB_TAG_EXTERN | LIB_TAG_INDIRECT))) {
ob->mode &= ~(OB_MODE_EDIT | OB_MODE_PARTICLE_EDIT);
ob->mode &= ~(OB_MODE_EDIT | OB_MODE_PARTICLE_EDIT | OB_MODE_HAIR_EDIT);
if (!fd->memfile) {
ob->mode &= ~OB_MODE_POSE;
}
@@ -5818,6 +5851,14 @@ static void lib_link_scene(FileData *fd, Main *main)
sce->toolsettings->particle.shape_object = newlibadr(fd, sce->id.lib, sce->toolsettings->particle.shape_object);
{
HairEditSettings *hair_edit = &sce->toolsettings->hair_edit;
if (hair_edit->brush)
hair_edit->brush = newlibadr(fd, sce->id.lib, hair_edit->brush);
if (hair_edit->shape_object)
hair_edit->shape_object = newlibadr(fd, sce->id.lib, hair_edit->shape_object);
}
for (BaseLegacy *base_legacy_next, *base_legacy = sce->base.first; base_legacy; base_legacy = base_legacy_next) {
base_legacy_next = base_legacy->next;
@@ -6130,7 +6171,8 @@ static void direct_link_scene(FileData *fd, Scene *sce, Main *bmain)
sce->toolsettings->particle.scene_layer = NULL;
sce->toolsettings->particle.object = NULL;
sce->toolsettings->gp_sculpt.paintcursor = NULL;
sce->toolsettings->hair_edit.paint_cursor = NULL;
/* in rare cases this is needed, see [#33806] */
if (sce->toolsettings->vpaint) {
sce->toolsettings->vpaint->vpaint_prev = NULL;

View File

@@ -121,6 +121,7 @@
#include "DNA_group_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_fileglobal_types.h"
#include "DNA_hair_types.h"
#include "DNA_key_types.h"
#include "DNA_lattice_types.h"
#include "DNA_lamp_types.h"
@@ -1706,6 +1707,23 @@ static void write_fmaps(WriteData *wd, ListBase *fbase)
}
}
static void write_hair(WriteData *wd, HairPattern *hair)
{
writestruct(wd, DATA, HairFollicle, hair->num_follicles, hair->follicles);
writelist(wd, DATA, HairGroup, &hair->groups);
for (HairGroup *group = hair->groups.first; group; group = group->next) {
const int (*parent_index)[4] = group->strands_parent_index;
const float (*parent_weight)[4] = group->strands_parent_weight;
if (parent_index) {
writedata(wd, DATA, sizeof(*parent_index) * group->num_follicles, parent_index);
}
if (parent_weight) {
writedata(wd, DATA, sizeof(*parent_weight) * group->num_follicles, parent_weight);
}
}
}
static void write_modifiers(WriteData *wd, ListBase *modbase)
{
ModifierData *md;
@@ -1877,6 +1895,14 @@ static void write_modifiers(WriteData *wd, ListBase *modbase)
}
}
}
else if (md->type == eModifierType_Hair) {
HairModifierData *hmd = (HairModifierData *)md;
if (hmd->hair) {
writestruct(wd, DATA, HairPattern, 1, hmd->hair);
write_hair(wd, hmd->hair);
}
}
}
}
@@ -2259,6 +2285,7 @@ static void write_mesh(WriteData *wd, Mesh *mesh)
CustomData_reset(&mesh->pdata);
CustomData_reset(&mesh->ldata);
mesh->edit_btmesh = NULL;
mesh->edit_strands = NULL;
/* now fill in polys to mfaces */
/* XXX This breaks writing design, by using temp allocated memory, which will likely generate

View File

@@ -119,6 +119,10 @@ set(SRC
intern/bmesh_queries.c
intern/bmesh_queries.h
intern/bmesh_queries_inline.h
intern/bmesh_strands.c
intern/bmesh_strands.h
intern/bmesh_strands_conv.c
intern/bmesh_strands_conv.h
intern/bmesh_structure.c
intern/bmesh_structure.h
intern/bmesh_structure_inline.h

View File

@@ -244,6 +244,8 @@ extern "C" {
#include "intern/bmesh_mesh_validate.h"
#include "intern/bmesh_mods.h"
#include "intern/bmesh_operators.h"
#include "intern/bmesh_strands.h"
#include "intern/bmesh_strands_conv.h"
#include "intern/bmesh_polygon.h"
#include "intern/bmesh_polygon_edgenet.h"
#include "intern/bmesh_queries.h"

View File

@@ -900,6 +900,34 @@ void BM_elem_float_data_set(CustomData *cd, void *element, int type, const float
if (f) *f = val;
}
float BM_elem_float_data_named_get(CustomData *cd, void *element, int type, const char *name)
{
const float *f = CustomData_bmesh_get_named(cd, ((BMHeader *)element)->data, type, name);
return f ? *f : 0.0f;
}
void BM_elem_float_data_named_set(CustomData *cd, void *element, int type, const char *name, const float val)
{
float *f = CustomData_bmesh_get_named(cd, ((BMHeader *)element)->data, type, name);
if (f) *f = val;
}
void BM_elem_meshsample_data_named_get(CustomData *cd, void *element, int type, const char *name, MeshSample *val)
{
const MeshSample *s = CustomData_bmesh_get_named(cd, ((BMHeader *)element)->data, type, name);
if (s)
memcpy(val, s, sizeof(MeshSample));
else
memset(val, 0, sizeof(MeshSample));
}
void BM_elem_meshsample_data_named_set(CustomData *cd, void *element, int type, const char *name, const MeshSample *val)
{
MeshSample *s = CustomData_bmesh_get_named(cd, ((BMHeader *)element)->data, type, name);
if (s)
memcpy(s, val, sizeof(MeshSample));
}
/** \name Loop interpolation functions: BM_vert_loop_groups_data_layer_***
*
* Handling loop custom-data such as UV's, while keeping contiguous fans is rather tedious.

View File

@@ -29,6 +29,8 @@
struct LinkNode;
struct MemArena;
struct MeshSample;
void BM_loop_interp_multires_ex(
BMesh *bm, BMLoop *l_dst, const BMFace *f_src,
@@ -54,6 +56,10 @@ void BM_data_layer_copy(BMesh *bm, CustomData *data, int type, int src_n, int d
float BM_elem_float_data_get(CustomData *cd, void *element, int type);
void BM_elem_float_data_set(CustomData *cd, void *element, int type, const float val);
float BM_elem_float_data_named_get(CustomData *cd, void *element, int type, const char *name);
void BM_elem_float_data_named_set(CustomData *cd, void *element, int type, const char *name, const float val);
void BM_elem_meshsample_data_named_get(CustomData *cd, void *element, int type, const char *name, struct MeshSample *val);
void BM_elem_meshsample_data_named_set(CustomData *cd, void *element, int type, const char *name, const struct MeshSample *val);
void BM_face_interp_from_face_ex(
BMesh *bm, BMFace *f_dst, const BMFace *f_src, const bool do_vertex,

View File

@@ -29,6 +29,8 @@
#ifndef __BMESH_ITERATORS_INLINE_H__
#define __BMESH_ITERATORS_INLINE_H__
#include "BLI_mempool.h"
/* inline here optimizes out the switch statement when called with
* constant values (which is very common), nicer for loop-in-loop situations */
@@ -43,7 +45,6 @@ BLI_INLINE void *BM_iter_step(BMIter *iter)
return iter->step(iter);
}
/**
* \brief Iterator Init
*

View File

@@ -98,6 +98,9 @@
#include "bmesh.h"
#include "intern/bmesh_private.h" /* for element checking */
/* XXX stupid hack: linker otherwise strips bmesh_strands_conv.c because it is not used inside bmesh */
void *__dummy_hack__ = &BM_strands_count_psys_keys;
void BM_mesh_cd_flag_ensure(BMesh *bm, Mesh *mesh, const char cd_flag)
{
const char cd_flag_all = BM_mesh_cd_flag_from_bmesh(bm) | cd_flag;
@@ -200,6 +203,7 @@ void BM_mesh_bm_from_me(
BMFace *f;
float (*keyco)[3] = NULL;
int totloops, i, j;
CustomDataMask mask = CD_MASK_BMESH | params->cd_mask_extra;
/* free custom data */
/* this isnt needed in most cases but do just incase */
@@ -210,10 +214,10 @@ void BM_mesh_bm_from_me(
if (!me || !me->totvert) {
if (me) { /*no verts? still copy customdata layout*/
CustomData_copy(&me->vdata, &bm->vdata, CD_MASK_BMESH, CD_ASSIGN, 0);
CustomData_copy(&me->edata, &bm->edata, CD_MASK_BMESH, CD_ASSIGN, 0);
CustomData_copy(&me->ldata, &bm->ldata, CD_MASK_BMESH, CD_ASSIGN, 0);
CustomData_copy(&me->pdata, &bm->pdata, CD_MASK_BMESH, CD_ASSIGN, 0);
CustomData_copy(&me->vdata, &bm->vdata, mask, CD_ASSIGN, 0);
CustomData_copy(&me->edata, &bm->edata, mask, CD_ASSIGN, 0);
CustomData_copy(&me->ldata, &bm->ldata, mask, CD_ASSIGN, 0);
CustomData_copy(&me->pdata, &bm->pdata, mask, CD_ASSIGN, 0);
CustomData_bmesh_init_pool(&bm->vdata, me->totvert, BM_VERT);
CustomData_bmesh_init_pool(&bm->edata, me->totedge, BM_EDGE);
@@ -225,10 +229,10 @@ void BM_mesh_bm_from_me(
vtable = MEM_mallocN(sizeof(void **) * me->totvert, "mesh to bmesh vtable");
CustomData_copy(&me->vdata, &bm->vdata, CD_MASK_BMESH, CD_CALLOC, 0);
CustomData_copy(&me->edata, &bm->edata, CD_MASK_BMESH, CD_CALLOC, 0);
CustomData_copy(&me->ldata, &bm->ldata, CD_MASK_BMESH, CD_CALLOC, 0);
CustomData_copy(&me->pdata, &bm->pdata, CD_MASK_BMESH, CD_CALLOC, 0);
CustomData_copy(&me->vdata, &bm->vdata, mask, CD_CALLOC, 0);
CustomData_copy(&me->edata, &bm->edata, mask, CD_CALLOC, 0);
CustomData_copy(&me->ldata, &bm->ldata, mask, CD_CALLOC, 0);
CustomData_copy(&me->pdata, &bm->pdata, mask, CD_CALLOC, 0);
if ((params->active_shapekey != 0) && (me->key != NULL)) {
actkey = BLI_findlink(&me->key->block, params->active_shapekey - 1);

View File

@@ -32,7 +32,10 @@
* \ingroup bmesh
*/
#include "BLI_sys_types.h"
struct Mesh;
typedef uint64_t CustomDataMask;
void BM_mesh_cd_validate(BMesh *bm);
void BM_mesh_cd_flag_ensure(BMesh *bm, struct Mesh *mesh, const char cd_flag);
@@ -48,6 +51,7 @@ struct BMeshFromMeshParams {
uint use_shapekey : 1;
/* define the active shape key (index + 1) */
int active_shapekey;
int64_t cd_mask_extra;
};
void BM_mesh_bm_from_me(
BMesh *bm, struct Mesh *me,

View File

@@ -54,6 +54,7 @@ void bmo_create_icosphere_exec(BMesh *bm, BMOperator *op);
void bmo_create_monkey_exec(BMesh *bm, BMOperator *op);
void bmo_create_uvsphere_exec(BMesh *bm, BMOperator *op);
void bmo_create_vert_exec(BMesh *bm, BMOperator *op);
//void bmo_create_strand_exec(BMesh *bm, BMOperator *op);
void bmo_delete_exec(BMesh *bm, BMOperator *op);
void bmo_dissolve_edges_exec(BMesh *bm, BMOperator *op);
void bmo_dissolve_faces_exec(BMesh *bm, BMOperator *op);

View File

@@ -0,0 +1,158 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Contributor(s): Lukas Toenne.
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/bmesh/intern/bmesh_strands.c
* \ingroup bmesh
*/
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
#include "BLI_math.h"
#include "BLI_mempool.h"
#include "bmesh.h"
#include "bmesh_private.h"
/*
* STRANDS OF MESH CALLBACKS
*/
void bmstranditer__strands_of_mesh_begin(struct BMIter__elem_of_mesh *iter)
{
BLI_mempool_iternew(iter->pooliter.pool, &iter->pooliter);
}
void *bmstranditer__strands_of_mesh_step(struct BMIter__elem_of_mesh *iter)
{
BMVert *v;
do {
v = BLI_mempool_iterstep(&iter->pooliter);
} while (v && !BM_strands_vert_is_root(v));
return v;
}
/*
* VERTS OF STRAND CALLBACKS
*/
/* BMIter__vert_of_strand is not included in the union in BMIter, just make sure it is big enough */
BLI_STATIC_ASSERT(sizeof(BMIter__vert_of_strand) <= sizeof(BMIter), "BMIter must be at least as large as BMIter__vert_of_strand")
void bmstranditer__verts_of_strand_begin(struct BMIter__vert_of_strand *iter)
{
iter->e_next = iter->v_next->e;
}
void *bmstranditer__verts_of_strand_step(struct BMIter__vert_of_strand *iter)
{
BMVert *v_curr = iter->v_next;
if (iter->e_next) {
BMEdge *e_first = iter->e_next;
/* select the other vertex of the current edge */
iter->v_next = (iter->v_next == iter->e_next->v1 ? iter->e_next->v2 : iter->e_next->v1);
/* select the next edge of the current vertex */
iter->e_next = bmesh_disk_edge_next(iter->e_next, iter->v_next);
if (iter->e_next == e_first) {
/* only one edge means the last segment, terminate */
iter->e_next = NULL;
}
}
else
iter->v_next = NULL; /* last vertex, terminate */
return v_curr;
}
/* ------------------------------------------------------------------------- */
int BM_strands_count(BMesh *bm)
{
BMVert *v;
BMIter iter;
int count = 0;
BM_ITER_STRANDS(v, &iter, bm, BM_STRANDS_OF_MESH) {
++count;
}
return count;
}
int BM_strands_keys_count(BMVert *root)
{
BMVert *v;
BMIter iter;
int count = 0;
BM_ITER_STRANDS_ELEM(v, &iter, root, BM_VERTS_OF_STRAND) {
++count;
}
return count;
}
int BM_strands_keys_count_max(BMesh *bm)
{
BMVert *v;
BMIter iter;
int maxkeys = 0;
BM_ITER_STRANDS(v, &iter, bm, BM_STRANDS_OF_MESH) {
int n = BM_strands_keys_count(v);
if (n > maxkeys)
maxkeys = n;
}
return maxkeys;
}
/* ------------------------------------------------------------------------- */
/* Create a new strand */
BMVert *BM_strands_create(BMesh *bm, int len, bool set_defaults)
{
float co[3] = {0.0f, 0.0f, 0.0f};
BMVert *root, *v, *vprev;
int k;
for (k = 0; k < len; ++k) {
vprev = v;
v = BM_vert_create(bm, co, NULL, set_defaults ? BM_CREATE_NOP : BM_CREATE_SKIP_CD);
zero_v3(v->no);
/* root */
if (k == 0) {
root = v;
}
else {
/*BMEdge *e =*/ BM_edge_create(bm, vprev, v, NULL, set_defaults ? BM_CREATE_NOP : BM_CREATE_SKIP_CD);
}
}
return root;
}

View File

@@ -0,0 +1,216 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Contributor(s): Lukas Toenne.
*
* ***** END GPL LICENSE BLOCK *****
*/
#ifndef __BMESH_STRANDS_H__
#define __BMESH_STRANDS_H__
/** \file blender/bmesh/intern/bmesh_strands.h
* \ingroup bmesh
*/
#include "BLI_utildefines.h"
#include "bmesh.h"
#include "bmesh_queries.h"
#include "bmesh_structure.h"
/* True if v is the root of a strand */
BLI_INLINE bool BM_strands_vert_is_root(BMVert *v)
{
BMEdge *e_first = v->e;
BMEdge *e_next;
if (!e_first)
return true; /* single vertex is both root and tip */
e_next = bmesh_disk_edge_next(e_first, v);
/* with a single edge, the vertex is either first or last of the curve;
* first vertex is defined as the root
*/
if (e_next == e_first) {
if (e_first->v1 == v)
return true;
}
return false;
}
/* True if v is the tip of a strand */
BLI_INLINE bool BM_strands_vert_is_tip(BMVert *v)
{
BMEdge *e_first = v->e;
BMEdge *e_next;
if (!e_first)
return true; /* single vertex is both root and tip */
e_next = bmesh_disk_edge_next(e_first, v);
/* with a single edge, the vertex is either first or last of the curve;
* last vertex is defined as the tip
*/
if (e_next == e_first) {
if (e_first->v2 == v)
return true;
}
return false;
}
/* Next vertex on a strand */
BLI_INLINE BMVert *BM_strands_vert_next(BMVert *v)
{
BMEdge *e_first = v->e;
BMEdge *e_next;
/* one of the edges leads to the previous vertex */
if (e_first) {
if (e_first->v1 == v)
return e_first->v2;
e_next = bmesh_disk_edge_next(e_first, v);
if (e_next->v1 == v)
return e_next->v2;
}
return NULL;
}
/* Previous vertex on a strand */
BLI_INLINE BMVert *BM_strands_vert_prev(BMVert *v)
{
BMEdge *e_first = v->e;
BMEdge *e_next;
/* one of the edges leads to the previous vertex */
if (e_first) {
if (e_first->v2 == v)
return e_first->v1;
e_next = bmesh_disk_edge_next(e_first, v);
if (e_next->v2 == v)
return e_next->v1;
}
return NULL;
}
int BM_strands_count(BMesh *bm);
int BM_strands_keys_count(BMVert *root);
int BM_strands_keys_count_max(BMesh *bm);
/* Create a new strand */
struct BMVert *BM_strands_create(struct BMesh *bm, int len, bool set_defaults);
/* ==== Iterators ==== */
typedef enum BMStrandsIterType {
BM_STRANDS_OF_MESH,
BM_VERTS_OF_STRAND,
} BMStrandsIterType;
#define BM_ITER_STRANDS(ele, iter, bm, itype) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_strand_iter_new(iter, bm, itype, NULL); \
ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_step(iter))
#define BM_ITER_STRANDS_INDEX(ele, iter, bm, itype, indexvar) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_strand_iter_new(iter, bm, itype, NULL), indexvar = 0; \
ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_step(iter), (indexvar)++)
#define BM_ITER_STRANDS_ELEM(ele, iter, data, itype) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_strand_iter_new(iter, NULL, itype, data); \
ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_step(iter))
#define BM_ITER_STRANDS_ELEM_INDEX(ele, iter, data, itype, indexvar) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_strand_iter_new(iter, NULL, itype, data), indexvar = 0; \
ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_step(iter), (indexvar)++)
typedef struct BMIter__vert_of_strand {
BMVert *v_next;
BMEdge *e_next;
} BMIter__vert_of_strand;
void bmstranditer__strands_of_mesh_begin(struct BMIter__elem_of_mesh *iter);
void *bmstranditer__strands_of_mesh_step(struct BMIter__elem_of_mesh *iter);
void bmstranditer__verts_of_strand_begin(struct BMIter__vert_of_strand *iter);
void *bmstranditer__verts_of_strand_step(struct BMIter__vert_of_strand *iter);
BLI_INLINE bool BM_strand_iter_init(BMIter *iter, BMesh *bm, const char itype, void *data)
{
/* int argtype; */
iter->itype = itype;
/* inlining optimizes out this switch when called with the defined type */
switch ((BMStrandsIterType)itype) {
case BM_STRANDS_OF_MESH:
BLI_assert(bm != NULL);
BLI_assert(data == NULL);
iter->begin = (BMIter__begin_cb)bmstranditer__strands_of_mesh_begin;
iter->step = (BMIter__step_cb)bmstranditer__strands_of_mesh_step;
iter->data.elem_of_mesh.pooliter.pool = bm->vpool;
break;
case BM_VERTS_OF_STRAND: {
BMVert *root;
BLI_assert(data != NULL);
BLI_assert(((BMElem *)data)->head.htype == BM_VERT);
root = (BMVert *)data;
BLI_assert(BM_strands_vert_is_root(root));
iter->begin = (BMIter__begin_cb)bmstranditer__verts_of_strand_begin;
iter->step = (BMIter__step_cb)bmstranditer__verts_of_strand_step;
((BMIter__vert_of_strand *)(&iter->data))->v_next = root;
break;
}
default:
/* fallback to regular bmesh iterator */
return BM_iter_init(iter, bm, itype, data);
break;
}
iter->begin(iter);
return true;
}
/**
* \brief Iterator New
*
* Takes a bmesh iterator structure and fills
* it with the appropriate function pointers based
* upon its type and then calls BMeshIter_step()
* to return the first element of the iterator.
*
*/
BLI_INLINE void *BM_strand_iter_new(BMIter *iter, BMesh *bm, const char itype, void *data)
{
if (LIKELY(BM_strand_iter_init(iter, bm, itype, data))) {
return BM_iter_step(iter);
}
else {
return NULL;
}
}
#define BM_strand_iter_new(iter, bm, itype, data) \
(BM_ITER_CHECK_TYPE_DATA(data), BM_strand_iter_new(iter, bm, itype, data))
#endif /* __BMESH_STRANDS_H__ */

View File

@@ -0,0 +1,720 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Contributor(s): Lukas Toenne.
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/bmesh/intern/bmesh_strands_conv.c
* \ingroup bmesh
*
* BM mesh conversion functions.
*/
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "DNA_particle_types.h"
#include "DNA_key_types.h"
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BKE_customdata.h"
#include "BKE_key.h"
#include "BKE_main.h"
#include "BKE_mesh_sample.h"
#include "BKE_particle.h"
#include "bmesh.h"
#include "intern/bmesh_private.h" /* for element checking */
const char *CD_HAIR_SEGMENT_LENGTH = "HAIR_SEGMENT_LENGTH";
const char *CD_HAIR_MASS = "HAIR_MASS";
const char *CD_HAIR_WEIGHT = "HAIR_WEIGHT";
const char *CD_HAIR_ROOT_LOCATION = "HAIR_ROOT_LOCATION";
/* ------------------------------------------------------------------------- */
/**
* Currently this is only used for Python scripts
* which may fail to keep matching UV/TexFace layers.
*
* \note This should only perform any changes in exceptional cases,
* if we need this to be faster we could inline #BM_data_layer_add and only
* call #update_data_blocks once at the end.
*/
void BM_strands_cd_validate(BMesh *UNUSED(bm))
{
}
void BM_strands_cd_flag_ensure(BMesh *bm, const char cd_flag)
{
const char cd_flag_all = BM_strands_cd_flag_from_bmesh(bm) | cd_flag;
BM_strands_cd_flag_apply(bm, cd_flag_all);
}
void BM_strands_cd_flag_apply(BMesh *bm, const char UNUSED(cd_flag))
{
/* CustomData_bmesh_init_pool() must run first */
BLI_assert(bm->vdata.totlayer == 0 || bm->vdata.pool != NULL);
BLI_assert(bm->edata.totlayer == 0 || bm->edata.pool != NULL);
if (CustomData_get_named_layer_index(&bm->vdata, CD_PROP_FLT, CD_HAIR_MASS) < 0) {
BM_data_layer_add_named(bm, &bm->vdata, CD_PROP_FLT, CD_HAIR_MASS);
}
if (CustomData_get_named_layer_index(&bm->vdata, CD_PROP_FLT, CD_HAIR_WEIGHT) < 0) {
BM_data_layer_add_named(bm, &bm->vdata, CD_PROP_FLT, CD_HAIR_WEIGHT);
}
if (CustomData_get_named_layer_index(&bm->vdata, CD_MSURFACE_SAMPLE, CD_HAIR_ROOT_LOCATION) < 0) {
BM_data_layer_add_named(bm, &bm->vdata, CD_MSURFACE_SAMPLE, CD_HAIR_ROOT_LOCATION);
}
if (CustomData_get_named_layer_index(&bm->vdata, CD_PROP_FLT, CD_HAIR_SEGMENT_LENGTH) < 0) {
BM_data_layer_add_named(bm, &bm->vdata, CD_PROP_FLT, CD_HAIR_SEGMENT_LENGTH);
}
}
char BM_strands_cd_flag_from_bmesh(BMesh *UNUSED(bm))
{
char cd_flag = 0;
return cd_flag;
}
/* particles */
int BM_strands_count_psys_keys(ParticleSystem *psys)
{
ParticleData *pa;
int p;
int totkeys = 0;
for (p = 0, pa = psys->particles; p < psys->totpart; ++p, ++pa)
totkeys += pa->totkey;
return totkeys;
}
#if 0
static KeyBlock *bm_set_shapekey_from_psys(BMesh *bm, ParticleSystem *psys, int totvert, int act_key_nr)
{
KeyBlock *actkey, *block;
int i, j;
if (!psys->key) {
return NULL;
}
if (act_key_nr != 0)
actkey = BLI_findlink(&psys->key->block, act_key_nr - 1);
else
actkey = NULL;
CustomData_add_layer(&bm->vdata, CD_SHAPE_KEYINDEX, CD_ASSIGN, NULL, 0);
/* check if we need to generate unique ids for the shapekeys.
* this also exists in the file reading code, but is here for
* a sanity check */
if (!psys->key->uidgen) {
fprintf(stderr,
"%s had to generate shape key uid's in a situation we shouldn't need to! "
"(bmesh internal error)\n",
__func__);
psys->key->uidgen = 1;
for (block = psys->key->block.first; block; block = block->next) {
block->uid = psys->key->uidgen++;
}
}
if (actkey && actkey->totelem == totvert) {
bm->shapenr = act_key_nr;
}
for (i = 0, block = psys->key->block.first; block; block = block->next, i++) {
CustomData_add_layer_named(&bm->vdata, CD_SHAPEKEY,
CD_ASSIGN, NULL, 0, block->name);
j = CustomData_get_layer_index_n(&bm->vdata, CD_SHAPEKEY, i);
bm->vdata.layers[j].uid = block->uid;
}
return actkey;
}
#endif
/* create vertex and edge data for BMesh based on particle hair keys */
static void bm_make_particles(BMesh *bm, Object *ob, ParticleSystem *psys, struct DerivedMesh *emitter_dm, float (*keyco)[3], int UNUSED(cd_shape_keyindex_offset))
{
// KeyBlock *block;
ParticleData *pa;
HairKey *hkey;
int p, k;
int vindex, eindex;
BMVert *v = NULL, *v_prev;
BMEdge *e;
float hairmat[4][4];
/* XXX currently all particles and keys have the same mass, this may change */
float mass = psys->part->mass;
vindex = 0;
eindex = 0;
for (p = 0, pa = psys->particles; p < psys->totpart; ++p, ++pa) {
/* hair keys are in a local "hair space", but edit data should be in object space */
psys_mat_hair_to_object(ob, emitter_dm, psys->part->from, pa, hairmat);
for (k = 0, hkey = pa->hair; k < pa->totkey; ++k, ++hkey) {
float co[3];
copy_v3_v3(co, keyco ? keyco[vindex] : hkey->co);
mul_m4_v3(hairmat, co);
v_prev = v;
v = BM_vert_create(bm, co, NULL, BM_CREATE_SKIP_CD);
BM_elem_index_set(v, vindex); /* set_ok */
/* transfer flag */
// v->head.hflag = BM_vert_flag_from_mflag(mvert->flag & ~SELECT);
/* this is necessary for selection counts to work properly */
// if (hkey->editflag & SELECT) {
// BM_vert_select_set(bm, v, true);
// }
// normal_short_to_float_v3(v->no, mvert->no);
/* Copy Custom Data */
// CustomData_to_bmesh_block(&me->vdata, &bm->vdata, vindex, &v->head.data, true);
CustomData_bmesh_set_default(&bm->vdata, &v->head.data);
BM_elem_float_data_named_set(&bm->vdata, v, CD_PROP_FLT, CD_HAIR_MASS, mass);
BM_elem_float_data_named_set(&bm->vdata, v, CD_PROP_FLT, CD_HAIR_WEIGHT, hkey->weight);
/* root */
if (k == 0) {
MeshSample root_loc;
if (BKE_mesh_sample_from_particle(&root_loc, psys, emitter_dm, pa)) {
BM_elem_meshsample_data_named_set(&bm->vdata, v, CD_MSURFACE_SAMPLE, CD_HAIR_ROOT_LOCATION, &root_loc);
}
}
#if 0
/* set shapekey data */
if (psys->key) {
/* set shape key original index */
if (cd_shape_keyindex_offset != -1) BM_ELEM_CD_SET_INT(v, cd_shape_keyindex_offset, vindex);
for (block = psys->key->block.first, j = 0; block; block = block->next, j++) {
float *co = CustomData_bmesh_get_n(&bm->vdata, v->head.data, CD_SHAPEKEY, j);
if (co) {
copy_v3_v3(co, ((float *)block->data) + 3 * vindex);
}
}
}
#endif
vindex += 1;
if (k > 0) {
e = BM_edge_create(bm, v_prev, v, NULL, BM_CREATE_SKIP_CD);
BM_elem_index_set(e, eindex); /* set_ok; one less edge than vertices for each particle */
/* transfer flags */
// e->head.hflag = BM_edge_flag_from_mflag(medge->flag & ~SELECT);
/* this is necessary for selection counts to work properly */
// if (medge->flag & SELECT) {
// BM_edge_select_set(bm, e, true);
// }
/* Copy Custom Data */
// CustomData_to_bmesh_block(&me->edata, &bm->edata, eindex, &e->head.data, true);
CustomData_bmesh_set_default(&bm->edata, &e->head.data);
eindex += 1;
}
} /* hair keys */
} /* particles */
bm->elem_index_dirty &= ~(BM_VERT | BM_EDGE); /* added in order, clear dirty flag */
}
/**
* \brief ParticleSystem -> BMesh
*/
void BM_strands_bm_from_psys(BMesh *bm, Object *ob, ParticleSystem *psys, struct DerivedMesh *emitter_dm,
const bool set_key, int UNUSED(act_key_nr))
{
/*KeyBlock *actkey;*/
float (*keyco)[3] = NULL;
int totvert, totedge;
int cd_shape_keyindex_offset;
/* free custom data */
/* this isnt needed in most cases but do just incase */
CustomData_free(&bm->vdata, bm->totvert);
CustomData_free(&bm->edata, bm->totedge);
CustomData_free(&bm->ldata, bm->totloop);
CustomData_free(&bm->pdata, bm->totface);
totvert = BM_strands_count_psys_keys(psys);
totedge = totvert - psys->totpart;
if (!psys || !totvert || !totedge) {
if (psys) { /*no verts? still copy customdata layout*/
// CustomData_copy(&me->vdata, &bm->vdata, CD_MASK_BMESH, CD_ASSIGN, 0);
// CustomData_copy(&me->edata, &bm->edata, CD_MASK_BMESH, CD_ASSIGN, 0);
CustomData_bmesh_init_pool(&bm->vdata, totvert, BM_VERT);
CustomData_bmesh_init_pool(&bm->edata, totedge, BM_EDGE);
CustomData_bmesh_init_pool(&bm->ldata, 0, BM_LOOP);
CustomData_bmesh_init_pool(&bm->pdata, 0, BM_FACE);
}
return; /* sanity check */
}
#if 0
actkey = bm_set_shapekey_from_psys(bm, psys, totvert, act_key_nr);
if (actkey)
keyco = actkey->data;
#endif
CustomData_bmesh_init_pool(&bm->vdata, totvert, BM_VERT);
CustomData_bmesh_init_pool(&bm->edata, totedge, BM_EDGE);
BM_strands_cd_flag_apply(bm, /*psys->cd_flag*/0);
cd_shape_keyindex_offset = /*psys->key ? CustomData_get_offset(&bm->vdata, CD_SHAPE_KEYINDEX) :*/ -1;
bm_make_particles(bm, ob, psys, emitter_dm, set_key ? keyco : NULL, cd_shape_keyindex_offset);
#if 0 /* TODO */
if (me->mselect && me->totselect != 0) {
BMVert **vert_array = MEM_mallocN(sizeof(BMVert *) * bm->totvert, "VSelConv");
BMEdge **edge_array = MEM_mallocN(sizeof(BMEdge *) * bm->totedge, "ESelConv");
BMFace **face_array = MEM_mallocN(sizeof(BMFace *) * bm->totface, "FSelConv");
MSelect *msel;
#pragma omp parallel sections if (bm->totvert + bm->totedge + bm->totface >= BM_OMP_LIMIT)
{
#pragma omp section
{ BM_iter_as_array(bm, BM_VERTS_OF_MESH, NULL, (void **)vert_array, bm->totvert); }
#pragma omp section
{ BM_iter_as_array(bm, BM_EDGES_OF_MESH, NULL, (void **)edge_array, bm->totedge); }
#pragma omp section
{ BM_iter_as_array(bm, BM_FACES_OF_MESH, NULL, (void **)face_array, bm->totface); }
}
for (i = 0, msel = me->mselect; i < me->totselect; i++, msel++) {
switch (msel->type) {
case ME_VSEL:
BM_select_history_store(bm, (BMElem *)vert_array[msel->index]);
break;
case ME_ESEL:
BM_select_history_store(bm, (BMElem *)edge_array[msel->index]);
break;
case ME_FSEL:
BM_select_history_store(bm, (BMElem *)face_array[msel->index]);
break;
}
}
MEM_freeN(vert_array);
MEM_freeN(edge_array);
MEM_freeN(face_array);
}
else {
me->totselect = 0;
if (me->mselect) {
MEM_freeN(me->mselect);
me->mselect = NULL;
}
}
#endif
}
#if 0
/**
* \brief BMesh -> Mesh
*/
static BMVert **bm_to_mesh_vertex_map(BMesh *bm, int ototvert)
{
const int cd_shape_keyindex_offset = CustomData_get_offset(&bm->vdata, CD_SHAPE_KEYINDEX);
BMVert **vertMap = NULL;
BMVert *eve;
int i = 0;
BMIter iter;
/* caller needs to ensure this */
BLI_assert(ototvert > 0);
vertMap = MEM_callocN(sizeof(*vertMap) * ototvert, "vertMap");
if (cd_shape_keyindex_offset != -1) {
BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, i) {
const int keyi = BM_ELEM_CD_GET_INT(eve, cd_shape_keyindex_offset);
if ((keyi != ORIGINDEX_NONE) && (keyi < ototvert)) {
vertMap[keyi] = eve;
}
}
}
else {
BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, i) {
if (i < ototvert) {
vertMap[i] = eve;
}
else {
break;
}
}
}
return vertMap;
}
/**
* returns customdata shapekey index from a keyblock or -1
* \note could split this out into a more generic function */
static int bm_to_mesh_shape_layer_index_from_kb(BMesh *bm, KeyBlock *currkey)
{
int i;
int j = 0;
for (i = 0; i < bm->vdata.totlayer; i++) {
if (bm->vdata.layers[i].type == CD_SHAPEKEY) {
if (currkey->uid == bm->vdata.layers[i].uid) {
return j;
}
j++;
}
}
return -1;
}
BLI_INLINE void bmesh_quick_edgedraw_flag(MEdge *med, BMEdge *e)
{
/* this is a cheap way to set the edge draw, its not precise and will
* pick the first 2 faces an edge uses.
* The dot comparison is a little arbitrary, but set so that a 5 subd
* IcoSphere won't vanish but subd 6 will (as with pre-bmesh blender) */
if ( /* (med->flag & ME_EDGEDRAW) && */ /* assume to be true */
(e->l && (e->l != e->l->radial_next)) &&
(dot_v3v3(e->l->f->no, e->l->radial_next->f->no) > 0.9995f))
{
med->flag &= ~ME_EDGEDRAW;
}
else {
med->flag |= ME_EDGEDRAW;
}
}
#endif
static void make_particle_hair(BMesh *bm, BMVert *root, Object *ob, ParticleSystem *psys, struct DerivedMesh *emitter_dm, struct BVHTreeFromMesh *emitter_bvhtree, struct ParticleData *pa)
{
int totkey = BM_strands_keys_count(root);
HairKey *hair;
BMVert *v;
BMIter iter;
HairKey *hkey;
int k;
float inv_hairmat[4][4];
pa->alive = PARS_ALIVE;
pa->flag = 0;
pa->time = 0.0f;
pa->lifetime = 100.0f;
pa->dietime = 100.0f;
pa->size = psys->part->size;
// TODO define other particle stuff ...
hair = MEM_callocN(totkey * sizeof(HairKey), "hair keys");
hkey = hair;
k = 0;
BM_ITER_STRANDS_ELEM(v, &iter, root, BM_VERTS_OF_STRAND) {
/* root */
if (k == 0) {
MeshSample root_loc;
BM_elem_meshsample_data_named_get(&bm->vdata, v, CD_MSURFACE_SAMPLE, CD_HAIR_ROOT_LOCATION, &root_loc);
if (!BKE_mesh_sample_to_particle(&root_loc, psys, emitter_dm, emitter_bvhtree, pa)) {
pa->num = 0;
pa->num_dmcache = DMCACHE_NOTFOUND;
zero_v4(pa->fuv);
pa->foffset = 0.0f;
}
/* edit data is in object space, hair keys must be converted back into "hair space" */
psys_mat_hair_to_object(ob, emitter_dm, psys->part->from, pa, inv_hairmat);
invert_m4(inv_hairmat);
}
mul_v3_m4v3(hkey->co, inv_hairmat, v->co);
mul_v3_m4v3(hkey->world_co, ob->obmat, v->co);
hkey->time = totkey > 0 ? (float)k / (float)(totkey - 1) : 0.0f;
if (k == 0) {
/* weight 1.0 is used for pinning hair roots in particles */
hkey->weight = 1.0f;
}
else {
hkey->weight = BM_elem_float_data_named_get(&bm->vdata, v, CD_PROP_FLT, CD_HAIR_WEIGHT);
}
++hkey;
++k;
BM_CHECK_ELEMENT(v);
}
if (pa->hair)
MEM_freeN(pa->hair);
pa->hair = hair;
pa->totkey = totkey;
}
void BM_strands_bm_to_psys(BMesh *bm, Object *ob, ParticleSystem *psys, struct DerivedMesh *emitter_dm, struct BVHTreeFromMesh *emitter_bvhtree)
{
ParticleData *particles, *oldparticles;
int ototpart, ntotpart;
BMVert *root;
BMIter iter;
ParticleData *pa;
int p;
ototpart = psys->totpart;
ntotpart = BM_strands_count(bm);
/* new particles block */
if (bm->totvert == 0) particles = NULL;
else particles = MEM_callocN(ntotpart * sizeof(ParticleData), "particles");
/* lets save the old particles just in case we are actually working on
* a key ... we now do processing of the keys at the end */
oldparticles = psys->particles;
psys->totpart = ntotpart;
// psys->cd_flag = BM_strands_cd_flag_from_bmesh(bm);
pa = particles;
p = 0;
BM_ITER_STRANDS(root, &iter, bm, BM_STRANDS_OF_MESH) {
make_particle_hair(bm, root, ob, psys, emitter_dm, emitter_bvhtree, pa);
++pa;
++p;
}
bm->elem_index_dirty &= ~BM_VERT;
#if 0 // TODO
{
BMEditSelection *selected;
me->totselect = BLI_listbase_count(&(bm->selected));
if (me->mselect) MEM_freeN(me->mselect);
me->mselect = MEM_callocN(sizeof(MSelect) * me->totselect, "Mesh selection history");
for (i = 0, selected = bm->selected.first; selected; i++, selected = selected->next) {
if (selected->htype == BM_VERT) {
me->mselect[i].type = ME_VSEL;
}
else if (selected->htype == BM_EDGE) {
me->mselect[i].type = ME_ESEL;
}
else if (selected->htype == BM_FACE) {
me->mselect[i].type = ME_FSEL;
}
me->mselect[i].index = BM_elem_index_get(selected->ele);
}
}
#endif
#if 0 // TODO
/* see comment below, this logic is in twice */
if (me->key) {
const int cd_shape_keyindex_offset = CustomData_get_offset(&bm->vdata, CD_SHAPE_KEYINDEX);
KeyBlock *currkey;
KeyBlock *actkey = BLI_findlink(&me->key->block, bm->shapenr - 1);
float (*ofs)[3] = NULL;
/* go through and find any shapekey customdata layers
* that might not have corresponding KeyBlocks, and add them if
* necessary */
j = 0;
for (i = 0; i < bm->vdata.totlayer; i++) {
if (bm->vdata.layers[i].type != CD_SHAPEKEY)
continue;
for (currkey = me->key->block.first; currkey; currkey = currkey->next) {
if (currkey->uid == bm->vdata.layers[i].uid)
break;
}
if (!currkey) {
currkey = BKE_keyblock_add(me->key, bm->vdata.layers[i].name);
currkey->uid = bm->vdata.layers[i].uid;
}
j++;
}
/* editing the base key should update others */
if ((me->key->type == KEY_RELATIVE) && /* only need offsets for relative shape keys */
(actkey != NULL) && /* unlikely, but the active key may not be valid if the
* bmesh and the mesh are out of sync */
(oldverts != NULL)) /* not used here, but 'oldverts' is used later for applying 'ofs' */
{
const bool act_is_basis = BKE_keyblock_is_basis(me->key, bm->shapenr - 1);
/* active key is a base */
if (act_is_basis && (cd_shape_keyindex_offset != -1)) {
float (*fp)[3] = actkey->data;
ofs = MEM_callocN(sizeof(float) * 3 * bm->totvert, "currkey->data");
mvert = me->mvert;
BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, i) {
const int keyi = BM_ELEM_CD_GET_INT(eve, cd_shape_keyindex_offset);
if (keyi != ORIGINDEX_NONE) {
sub_v3_v3v3(ofs[i], mvert->co, fp[keyi]);
}
else {
/* if there are new vertices in the mesh, we can't propagate the offset
* because it will only work for the existing vertices and not the new
* ones, creating a mess when doing e.g. subdivide + translate */
MEM_freeN(ofs);
ofs = NULL;
break;
}
mvert++;
}
}
}
for (currkey = me->key->block.first; currkey; currkey = currkey->next) {
const bool apply_offset = (ofs && (currkey != actkey) && (bm->shapenr - 1 == currkey->relative));
int cd_shape_offset;
int keyi;
float (*ofs_pt)[3] = ofs;
float *newkey, (*oldkey)[3], *fp;
j = bm_to_mesh_shape_layer_index_from_kb(bm, currkey);
cd_shape_offset = CustomData_get_n_offset(&bm->vdata, CD_SHAPEKEY, j);
fp = newkey = MEM_callocN(me->key->elemsize * bm->totvert, "currkey->data");
oldkey = currkey->data;
mvert = me->mvert;
BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
if (currkey == actkey) {
copy_v3_v3(fp, eve->co);
if (actkey != me->key->refkey) { /* important see bug [#30771] */
if (cd_shape_keyindex_offset != -1) {
if (oldverts) {
keyi = BM_ELEM_CD_GET_INT(eve, cd_shape_keyindex_offset);
if (keyi != ORIGINDEX_NONE && keyi < currkey->totelem) { /* valid old vertex */
copy_v3_v3(mvert->co, oldverts[keyi].co);
}
}
}
}
}
else if (j != -1) {
/* in most cases this runs */
copy_v3_v3(fp, BM_ELEM_CD_GET_VOID_P(eve, cd_shape_offset));
}
else if ((oldkey != NULL) &&
(cd_shape_keyindex_offset != -1) &&
((keyi = BM_ELEM_CD_GET_INT(eve, cd_shape_keyindex_offset)) != ORIGINDEX_NONE) &&
(keyi < currkey->totelem))
{
/* old method of reconstructing keys via vertice's original key indices,
* currently used if the new method above fails (which is theoretically
* possible in certain cases of undo) */
copy_v3_v3(fp, oldkey[keyi]);
}
else {
/* fail! fill in with dummy value */
copy_v3_v3(fp, mvert->co);
}
/* propagate edited basis offsets to other shapes */
if (apply_offset) {
add_v3_v3(fp, *ofs_pt++);
}
fp += 3;
mvert++;
}
currkey->totelem = bm->totvert;
if (currkey->data) {
MEM_freeN(currkey->data);
}
currkey->data = newkey;
}
if (ofs) MEM_freeN(ofs);
}
#else
psys->particles = particles;
#endif
if (oldparticles) {
ParticleData *opa;
int op;
for (op = 0, opa = oldparticles; op < ototpart; ++op, ++opa)
if (opa->hair)
MEM_freeN(opa->hair);
MEM_freeN(oldparticles);
}
}

View File

@@ -0,0 +1,62 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2014 Blender Foundation.
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Lukas Toenne.
*
* ***** END GPL LICENSE BLOCK *****
*/
#ifndef __BMESH_STRANDS_CONV_H__
#define __BMESH_STRANDS_CONV_H__
/** \file blender/bmesh/intern/bmesh_strands_conv.h
* \ingroup bmesh
*/
struct BMesh;
struct Mesh;
struct Object;
struct ParticleSystem;
struct DerivedMesh;
struct BVHTreeFromMesh;
extern const char *CD_HAIR_SEGMENT_LENGTH;
extern const char *CD_HAIR_MASS;
extern const char *CD_HAIR_WEIGHT;
extern const char *CD_HAIR_ROOT_LOCATION;
void BM_strands_cd_validate(struct BMesh *bm);
void BM_strands_cd_flag_ensure(struct BMesh *bm, const char cd_flag);
void BM_strands_cd_flag_apply(struct BMesh *bm, const char cd_flag);
char BM_strands_cd_flag_from_bmesh(struct BMesh *bm);
/* particles */
int BM_strands_count_psys_keys(struct ParticleSystem *psys);
void BM_strands_bm_from_psys(struct BMesh *bm, struct Object *ob, struct ParticleSystem *psys, struct DerivedMesh *emitter_dm,
const bool set_key, int act_key_nr);
void BM_strands_bm_to_psys(struct BMesh *bm, struct Object *ob, struct ParticleSystem *psys, struct DerivedMesh *emitter_dm, struct BVHTreeFromMesh *emitter_bvhtree);
#define BMALLOC_TEMPLATE_FROM_PSYS(psys) { (CHECK_TYPE_INLINE(psys, ParticleSystem *), \
BM_strands_count_psys_keys(psys)), (BM_strands_count_psys_keys(psys) - (psys)->totpart), 0, 0 }
#endif /* __BMESH_STRANDS_CONV_H__ */

View File

@@ -57,10 +57,13 @@ set(SRC
intern/draw_cache.c
intern/draw_cache_impl_curve.c
intern/draw_cache_impl_displist.c
intern/draw_cache_impl_hair.c
intern/draw_cache_impl_lattice.c
intern/draw_cache_impl_mesh.c
intern/draw_cache_impl_particles.c
intern/draw_cache_impl_strands.c
intern/draw_common.c
intern/draw_hair.c
intern/draw_manager.c
intern/draw_manager_text.c
intern/draw_manager_profiling.c
@@ -70,6 +73,7 @@ set(SRC
modes/edit_lattice_mode.c
modes/edit_mesh_mode.c
modes/edit_metaball_mode.c
modes/edit_strands_mode.c
modes/edit_surface_mode.c
modes/edit_text_mode.c
modes/object_mode.c
@@ -122,6 +126,7 @@ data_to_c_simple(engines/eevee/shaders/default_world_frag.glsl SRC)
data_to_c_simple(engines/eevee/shaders/background_vert.glsl SRC)
data_to_c_simple(engines/eevee/shaders/ambient_occlusion_lib.glsl SRC)
data_to_c_simple(engines/eevee/shaders/effect_minmaxz_frag.glsl SRC)
data_to_c_simple(engines/eevee/shaders/hair_lib.glsl SRC)
data_to_c_simple(engines/eevee/shaders/lamps_lib.glsl SRC)
data_to_c_simple(engines/eevee/shaders/lightprobe_lib.glsl SRC)
data_to_c_simple(engines/eevee/shaders/lightprobe_filter_glossy_frag.glsl SRC)
@@ -185,6 +190,7 @@ data_to_c_simple(modes/shaders/edit_lattice_overlay_frag.glsl SRC)
data_to_c_simple(modes/shaders/edit_lattice_overlay_loosevert_vert.glsl SRC)
data_to_c_simple(modes/shaders/edit_normals_vert.glsl SRC)
data_to_c_simple(modes/shaders/edit_normals_geom.glsl SRC)
data_to_c_simple(modes/shaders/edit_strands_vert.glsl SRC)
data_to_c_simple(modes/shaders/object_empty_image_frag.glsl SRC)
data_to_c_simple(modes/shaders/object_empty_image_vert.glsl SRC)
data_to_c_simple(modes/shaders/object_outline_resolve_frag.glsl SRC)

View File

@@ -25,19 +25,25 @@
#include "DRW_render.h"
#include "DNA_world_types.h"
#include "DNA_hair_types.h"
#include "DNA_modifier_types.h"
#include "DNA_view3d_types.h"
#include "DNA_world_types.h"
#include "BLI_dynstr.h"
#include "BLI_ghash.h"
#include "BLI_alloca.h"
#include "BKE_DerivedMesh.h"
#include "BKE_editstrands.h"
#include "BKE_particle.h"
#include "BKE_paint.h"
#include "BKE_pbvh.h"
#include "DEG_depsgraph.h"
#include "GPU_material.h"
#include "GPU_texture.h"
#include "eevee_engine.h"
#include "eevee_lut.h"
@@ -70,6 +76,8 @@ static struct {
struct GPUShader *default_prepass_sh;
struct GPUShader *default_prepass_clip_sh;
struct GPUShader *default_prepass_hair_fiber_sh;
struct GPUShader *default_prepass_hair_fiber_clip_sh;
struct GPUShader *default_lit[VAR_MAT_MAX];
struct GPUShader *default_background;
@@ -106,6 +114,7 @@ extern char datatoc_lightprobe_geom_glsl[];
extern char datatoc_lightprobe_vert_glsl[];
extern char datatoc_background_vert_glsl[];
extern char datatoc_volumetric_frag_glsl[];
extern char datatoc_hair_lib_glsl[];
extern Material defmaterial;
extern GlobalsUboStorage ts;
@@ -282,6 +291,9 @@ static char *eevee_get_defines(int options)
if ((options & VAR_MAT_HAIR) != 0) {
BLI_dynstr_appendf(ds, "#define HAIR_SHADER\n");
}
if ((options & VAR_MAT_HAIR_FIBERS) != 0) {
BLI_dynstr_append(ds, DRW_hair_shader_defines());
}
if ((options & VAR_MAT_PROBE) != 0) {
BLI_dynstr_appendf(ds, "#define PROBE_CAPTURE\n");
}
@@ -397,60 +409,87 @@ static void add_standard_uniforms(
static void create_default_shader(int options)
{
DynStr *ds_frag = BLI_dynstr_new();
BLI_dynstr_append(ds_frag, e_data.frag_shader_lib);
BLI_dynstr_append(ds_frag, datatoc_default_frag_glsl);
char *frag_str = BLI_dynstr_get_cstring(ds_frag);
BLI_dynstr_free(ds_frag);
char *vert_str = NULL;
{
DynStr *ds_vert = BLI_dynstr_new();
BLI_dynstr_append(ds_vert, datatoc_hair_lib_glsl);
BLI_dynstr_append(ds_vert, datatoc_lit_surface_vert_glsl);
vert_str = BLI_dynstr_get_cstring(ds_vert);
BLI_dynstr_free(ds_vert);
}
char *frag_str = NULL;
{
DynStr *ds_frag = BLI_dynstr_new();
BLI_dynstr_append(ds_frag, e_data.frag_shader_lib);
BLI_dynstr_append(ds_frag, datatoc_default_frag_glsl);
frag_str = BLI_dynstr_get_cstring(ds_frag);
BLI_dynstr_free(ds_frag);
}
char *defines = eevee_get_defines(options);
e_data.default_lit[options] = DRW_shader_create(datatoc_lit_surface_vert_glsl, NULL, frag_str, defines);
e_data.default_lit[options] = DRW_shader_create(vert_str, NULL, frag_str, defines);
MEM_freeN(defines);
MEM_freeN(vert_str);
MEM_freeN(frag_str);
}
void EEVEE_materials_init(EEVEE_StorageList *stl)
{
if (!e_data.frag_shader_lib) {
char *frag_str = NULL;
/* Shaders */
DynStr *ds_frag = BLI_dynstr_new();
BLI_dynstr_append(ds_frag, datatoc_bsdf_common_lib_glsl);
{
DynStr *ds_frag = BLI_dynstr_new();
BLI_dynstr_append(ds_frag, datatoc_bsdf_common_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_bsdf_sampling_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_ambient_occlusion_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_ambient_occlusion_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_raytrace_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_ssr_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_octahedron_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_irradiance_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_lightprobe_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_ltc_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_bsdf_direct_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_lamps_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_lit_surface_frag_glsl);
e_data.frag_shader_lib = BLI_dynstr_get_cstring(ds_frag);
BLI_dynstr_free(ds_frag);
BLI_dynstr_append(ds_frag, datatoc_octahedron_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_irradiance_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_lightprobe_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_ltc_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_bsdf_direct_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_lamps_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_lit_surface_frag_glsl);
e_data.frag_shader_lib = BLI_dynstr_get_cstring(ds_frag);
BLI_dynstr_free(ds_frag);
}
{
DynStr *ds_frag = BLI_dynstr_new();
BLI_dynstr_append(ds_frag, datatoc_bsdf_common_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_ambient_occlusion_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_octahedron_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_irradiance_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_lightprobe_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_ltc_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_bsdf_direct_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_lamps_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_volumetric_frag_glsl);
e_data.volume_shader_lib = BLI_dynstr_get_cstring(ds_frag);
BLI_dynstr_free(ds_frag);
}
ds_frag = BLI_dynstr_new();
BLI_dynstr_append(ds_frag, datatoc_bsdf_common_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_ambient_occlusion_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_octahedron_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_irradiance_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_lightprobe_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_ltc_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_bsdf_direct_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_lamps_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_volumetric_frag_glsl);
e_data.volume_shader_lib = BLI_dynstr_get_cstring(ds_frag);
BLI_dynstr_free(ds_frag);
char *hair_fiber_vert_str = NULL;
{
DynStr *ds_vert = BLI_dynstr_new();
BLI_dynstr_append(ds_vert, datatoc_hair_lib_glsl);
BLI_dynstr_append(ds_vert, datatoc_prepass_vert_glsl);
hair_fiber_vert_str = BLI_dynstr_get_cstring(ds_vert);
BLI_dynstr_free(ds_vert);
}
ds_frag = BLI_dynstr_new();
BLI_dynstr_append(ds_frag, e_data.frag_shader_lib);
BLI_dynstr_append(ds_frag, datatoc_default_frag_glsl);
frag_str = BLI_dynstr_get_cstring(ds_frag);
BLI_dynstr_free(ds_frag);
char *frag_str = NULL;
{
DynStr *ds_frag = BLI_dynstr_new();
BLI_dynstr_append(ds_frag, e_data.frag_shader_lib);
BLI_dynstr_append(ds_frag, datatoc_default_frag_glsl);
frag_str = BLI_dynstr_get_cstring(ds_frag);
BLI_dynstr_free(ds_frag);
}
e_data.default_background = DRW_shader_create(
datatoc_background_vert_glsl, NULL, datatoc_default_world_frag_glsl,
@@ -464,7 +503,19 @@ void EEVEE_materials_init(EEVEE_StorageList *stl)
datatoc_prepass_vert_glsl, NULL, datatoc_prepass_frag_glsl,
"#define CLIP_PLANES\n");
e_data.default_prepass_hair_fiber_sh = DRW_shader_create(
hair_fiber_vert_str, NULL, datatoc_prepass_frag_glsl, DRW_hair_shader_defines());
{
char defines[256];
BLI_snprintf(defines, sizeof(defines), "#define CLIP_PLANES\n%s",
DRW_hair_shader_defines());
e_data.default_prepass_hair_fiber_clip_sh = DRW_shader_create(
hair_fiber_vert_str, NULL, datatoc_prepass_frag_glsl, defines);
}
MEM_freeN(frag_str);
MEM_freeN(hair_fiber_vert_str);
/* Textures */
const int layers = 3 + 16;
@@ -680,23 +731,35 @@ struct GPUMaterial *EEVEE_material_mesh_depth_get(
}
struct GPUMaterial *EEVEE_material_hair_get(
struct Scene *scene, Material *ma)
struct Scene *scene, Material *ma, bool use_fibers)
{
const void *engine = &DRW_engine_viewport_eevee_type;
int options = VAR_MAT_MESH | VAR_MAT_HAIR;
int options = VAR_MAT_HAIR | VAR_MAT_MESH;
if (use_fibers) {
options |= VAR_MAT_HAIR_FIBERS;
}
GPUMaterial *mat = GPU_material_from_nodetree_find(&ma->gpumaterial, engine, options);
if (mat) {
return mat;
}
char *vert_str = NULL;
{
DynStr *ds_vert = BLI_dynstr_new();
BLI_dynstr_append(ds_vert, datatoc_hair_lib_glsl);
BLI_dynstr_append(ds_vert, datatoc_lit_surface_vert_glsl);
vert_str = BLI_dynstr_get_cstring(ds_vert);
BLI_dynstr_free(ds_vert);
}
char *defines = eevee_get_defines(options);
mat = GPU_material_from_nodetree(
scene, ma->nodetree, &ma->gpumaterial, engine, options,
datatoc_lit_surface_vert_glsl, NULL, e_data.frag_shader_lib,
vert_str, NULL, e_data.frag_shader_lib,
defines);
MEM_freeN(vert_str);
MEM_freeN(defines);
return mat;
@@ -707,13 +770,14 @@ struct GPUMaterial *EEVEE_material_hair_get(
**/
static struct DRWShadingGroup *EEVEE_default_shading_group_create(
EEVEE_SceneLayerData *sldata, EEVEE_Data *vedata, DRWPass *pass,
bool is_hair, bool is_flat_normal, bool use_blend, bool use_ssr)
bool is_hair, bool is_hair_fibers, bool is_flat_normal, bool use_blend, bool use_ssr)
{
static int ssr_id;
ssr_id = (use_ssr) ? 0 : -1;
int options = VAR_MAT_MESH;
if (is_hair) options |= VAR_MAT_HAIR;
if (is_hair_fibers) options |= VAR_MAT_HAIR_FIBERS;
if (is_flat_normal) options |= VAR_MAT_FLAT;
if (use_blend) options |= VAR_MAT_BLEND;
@@ -732,13 +796,14 @@ static struct DRWShadingGroup *EEVEE_default_shading_group_create(
**/
static struct DRWShadingGroup *EEVEE_default_shading_group_get(
EEVEE_SceneLayerData *sldata, EEVEE_Data *vedata,
bool is_hair, bool is_flat_normal, bool use_ssr)
bool is_hair, bool is_hair_fibers, bool is_flat_normal, bool use_ssr)
{
static int ssr_id;
ssr_id = (use_ssr) ? 0 : -1;
int options = VAR_MAT_MESH;
if (is_hair) options |= VAR_MAT_HAIR;
if (is_hair_fibers) options |= VAR_MAT_HAIR_FIBERS;
if (is_flat_normal) options |= VAR_MAT_FLAT;
if (e_data.default_lit[options] == NULL) {
@@ -746,7 +811,8 @@ static struct DRWShadingGroup *EEVEE_default_shading_group_get(
}
if (vedata->psl->default_pass[options] == NULL) {
DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL | DRW_STATE_CLIP_PLANES | DRW_STATE_WIRE;
//DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL | DRW_STATE_CLIP_PLANES | DRW_STATE_WIRE;
DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL;
vedata->psl->default_pass[options] = DRW_pass_create("Default Lit Pass", state);
DRWShadingGroup *shgrp = DRW_shgroup_create(e_data.default_lit[options], vedata->psl->default_pass[options]);
@@ -822,18 +888,22 @@ void EEVEE_materials_cache_init(EEVEE_Data *vedata)
DRWState state = DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS | DRW_STATE_WIRE;
psl->depth_pass = DRW_pass_create("Depth Pass", state);
stl->g_data->depth_shgrp = DRW_shgroup_create(e_data.default_prepass_sh, psl->depth_pass);
stl->g_data->hair_fibers_depth_shgrp = DRW_shgroup_create(e_data.default_prepass_hair_fiber_sh, psl->depth_pass);
state = DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS | DRW_STATE_CULL_BACK;
psl->depth_pass_cull = DRW_pass_create("Depth Pass Cull", state);
stl->g_data->depth_shgrp_cull = DRW_shgroup_create(e_data.default_prepass_sh, psl->depth_pass_cull);
stl->g_data->hair_fibers_depth_shgrp_cull = DRW_shgroup_create(e_data.default_prepass_hair_fiber_sh, psl->depth_pass_cull);
state = DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS | DRW_STATE_CLIP_PLANES | DRW_STATE_WIRE;
psl->depth_pass_clip = DRW_pass_create("Depth Pass Clip", state);
stl->g_data->depth_shgrp_clip = DRW_shgroup_create(e_data.default_prepass_clip_sh, psl->depth_pass_clip);
stl->g_data->hair_fibers_depth_shgrp_clip = DRW_shgroup_create(e_data.default_prepass_hair_fiber_clip_sh, psl->depth_pass_clip);
state = DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS | DRW_STATE_CLIP_PLANES | DRW_STATE_CULL_BACK;
psl->depth_pass_clip_cull = DRW_pass_create("Depth Pass Cull Clip", state);
stl->g_data->depth_shgrp_clip_cull = DRW_shgroup_create(e_data.default_prepass_clip_sh, psl->depth_pass_clip_cull);
stl->g_data->hair_fibers_depth_shgrp_clip_cull = DRW_shgroup_create(e_data.default_prepass_hair_fiber_clip_sh, psl->depth_pass_clip_cull);
}
{
@@ -970,7 +1040,7 @@ static void material_opaque(
/* Fallback to default shader */
if (*shgrp == NULL) {
*shgrp = EEVEE_default_shading_group_get(sldata, vedata, false, use_flat_nor, stl->effects->use_ssr);
*shgrp = EEVEE_default_shading_group_get(sldata, vedata, false, false, use_flat_nor, stl->effects->use_ssr);
DRW_shgroup_uniform_vec3(*shgrp, "basecol", color_p, 1);
DRW_shgroup_uniform_float(*shgrp, "metallic", metal_p, 1);
DRW_shgroup_uniform_float(*shgrp, "specular", spec_p, 1);
@@ -1035,7 +1105,7 @@ static void material_transparent(
if (*shgrp == NULL) {
*shgrp = EEVEE_default_shading_group_create(
sldata, vedata, psl->transparent_pass,
false, use_flat_nor, true, false);
false, false, use_flat_nor, true, false);
DRW_shgroup_uniform_vec3(*shgrp, "basecol", color_p, 1);
DRW_shgroup_uniform_float(*shgrp, "metallic", metal_p, 1);
DRW_shgroup_uniform_float(*shgrp, "specular", spec_p, 1);
@@ -1082,6 +1152,179 @@ static void material_transparent(
}
}
static void material_particle_hair(EEVEE_SceneLayerData *sldata, EEVEE_Data *vedata,
Object *ob, ParticleSystem *psys, ModifierData *md)
{
EEVEE_PassList *psl = ((EEVEE_Data *)vedata)->psl;
EEVEE_StorageList *stl = ((EEVEE_Data *)vedata)->stl;
const DRWContextState *draw_ctx = DRW_context_state_get();
Scene *scene = draw_ctx->scene;
GHash *material_hash = stl->g_data->hair_material_hash;
if (!psys_check_enabled(ob, psys, false)) {
return;
}
ParticleSettings *part = psys->part;
float mat[4][4];
unit_m4(mat);
bool use_hair = false;
struct Gwn_Batch *hair_geom = NULL;
if ((ob->mode & OB_MODE_HAIR_EDIT) == 0) {
int draw_as = (part->draw_as == PART_DRAW_REND) ? part->ren_as : part->draw_as;
if (draw_as == PART_DRAW_PATH && (psys->pathcache || psys->childcache)) {
use_hair = true;
hair_geom = DRW_cache_particles_get_hair(psys, md);
}
}
if (use_hair) {
Material *ma = give_current_material(ob, part->omat);
if (ma == NULL) {
ma = &defmaterial;
}
DRW_shgroup_call_add(stl->g_data->depth_shgrp, hair_geom, mat);
DRW_shgroup_call_add(stl->g_data->depth_shgrp_clip, hair_geom, mat);
DRWShadingGroup *shgrp = BLI_ghash_lookup(material_hash, (const void *)ma);
if (!shgrp) {
float *color_p = &ma->r;
float *metal_p = &ma->ray_mirror;
float *spec_p = &ma->spec;
float *rough_p = &ma->gloss_mir;
if (ma->use_nodes && ma->nodetree) {
struct GPUMaterial *gpumat = EEVEE_material_hair_get(scene, ma, false);
shgrp = DRW_shgroup_material_create(gpumat, psl->material_pass);
if (shgrp) {
add_standard_uniforms(shgrp, sldata, vedata, NULL, NULL, false);
BLI_ghash_insert(material_hash, ma, shgrp);
}
else {
/* Shader failed : pink color */
static float col[3] = {1.0f, 0.0f, 1.0f};
static float half = 0.5f;
color_p = col;
metal_p = spec_p = rough_p = &half;
}
}
/* Fallback to default shader */
if (shgrp == NULL) {
shgrp = EEVEE_default_shading_group_get(sldata, vedata, true, false, false, stl->effects->use_ssr);
DRW_shgroup_uniform_vec3(shgrp, "basecol", color_p, 1);
DRW_shgroup_uniform_float(shgrp, "metallic", metal_p, 1);
DRW_shgroup_uniform_float(shgrp, "specular", spec_p, 1);
DRW_shgroup_uniform_float(shgrp, "roughness", rough_p, 1);
BLI_ghash_insert(material_hash, ma, shgrp);
}
}
if (shgrp) {
DRW_shgroup_call_add(shgrp, hair_geom, mat);
}
}
}
static void material_hair(EEVEE_SceneLayerData *sldata, EEVEE_Data *vedata, Object *ob, HairGroup *group)
{
EEVEE_PassList *psl = ((EEVEE_Data *)vedata)->psl;
EEVEE_StorageList *stl = ((EEVEE_Data *)vedata)->stl;
const DRWContextState *draw_ctx = DRW_context_state_get();
Scene *scene = draw_ctx->scene;
GHash *material_hash = stl->g_data->hair_material_hash;
const HairEditSettings *tsettings = &scene->toolsettings->hair_edit;
float mat[4][4];
copy_m4_m4(mat, ob->obmat);
const DRWHairFiberTextureBuffer *fiber_buffer = NULL;
struct Gwn_Batch *hair_geom;
{
DerivedMesh *scalp = NULL;
if (ob->derivedFinal) {
scalp = ob->derivedFinal;
}
else {
EvaluationContext eval_ctx = {0};
DEG_evaluation_context_init(&eval_ctx, DAG_EVAL_VIEWPORT);
scalp = mesh_get_derived_final(&eval_ctx, scene, ob, CD_MASK_BAREMESH);
}
hair_geom = DRW_cache_hair_get_fibers(group, tsettings->hair_draw_subdiv, scalp, &fiber_buffer);
}
if (!group->draw_texture_cache) {
group->draw_texture_cache = DRW_texture_create_2D(fiber_buffer->width, fiber_buffer->height,
DRW_TEX_RG_32, 0, fiber_buffer->data);
}
GPUTexture **fiber_texture = (GPUTexture **)(&group->draw_texture_cache);
// TODO
Material *ma = NULL;/*give_current_material(ob, omat);*/
if (ma == NULL) {
ma = &defmaterial;
}
DRW_shgroup_call_add(stl->g_data->hair_fibers_depth_shgrp, hair_geom, mat);
DRW_hair_shader_uniforms(stl->g_data->hair_fibers_depth_shgrp, scene,
fiber_texture, fiber_buffer);
DRW_shgroup_call_add(stl->g_data->hair_fibers_depth_shgrp_clip, hair_geom, mat);
DRW_hair_shader_uniforms(stl->g_data->hair_fibers_depth_shgrp_clip, scene,
fiber_texture, fiber_buffer);
DRWShadingGroup *shgrp = BLI_ghash_lookup(material_hash, (const void *)ma);
if (!shgrp) {
float *color_p = &ma->r;
float *metal_p = &ma->ray_mirror;
float *spec_p = &ma->spec;
float *rough_p = &ma->gloss_mir;
if (ma->use_nodes && ma->nodetree) {
struct GPUMaterial *gpumat = EEVEE_material_hair_get(scene, ma, true);
shgrp = DRW_shgroup_material_create(gpumat, psl->material_pass);
if (shgrp) {
add_standard_uniforms(shgrp, sldata, vedata, NULL, NULL, false);
BLI_ghash_insert(material_hash, ma, shgrp);
}
else {
/* Shader failed : pink color */
static float col[3] = {1.0f, 0.0f, 1.0f};
static float half = 0.5f;
color_p = col;
metal_p = spec_p = rough_p = &half;
}
}
/* Fallback to default shader */
if (shgrp == NULL) {
shgrp = EEVEE_default_shading_group_get(sldata, vedata, true, true, false, stl->effects->use_ssr);
DRW_shgroup_uniform_vec3(shgrp, "basecol", color_p, 1);
DRW_shgroup_uniform_float(shgrp, "metallic", metal_p, 1);
DRW_shgroup_uniform_float(shgrp, "specular", spec_p, 1);
DRW_shgroup_uniform_float(shgrp, "roughness", rough_p, 1);
BLI_ghash_insert(material_hash, ma, shgrp);
}
}
if (shgrp) {
DRW_shgroup_call_add(shgrp, hair_geom, mat);
DRW_hair_shader_uniforms(shgrp, scene,
fiber_texture, fiber_buffer);
}
}
void EEVEE_materials_cache_populate(EEVEE_Data *vedata, EEVEE_SceneLayerData *sldata, Object *ob)
{
EEVEE_PassList *psl = ((EEVEE_Data *)vedata)->psl;
@@ -1211,77 +1454,15 @@ void EEVEE_materials_cache_populate(EEVEE_Data *vedata, EEVEE_SceneLayerData *sl
if (ob->type == OB_MESH) {
if (ob != draw_ctx->scene->obedit) {
material_hash = stl->g_data->hair_material_hash;
for (ModifierData *md = ob->modifiers.first; md; md = md->next) {
if (md->type == eModifierType_ParticleSystem) {
ParticleSystem *psys = ((ParticleSystemModifierData *)md)->psys;
if (psys_check_enabled(ob, psys, false)) {
ParticleSettings *part = psys->part;
int draw_as = (part->draw_as == PART_DRAW_REND) ? part->ren_as : part->draw_as;
if (draw_as == PART_DRAW_PATH && (psys->pathcache || psys->childcache)) {
struct Gwn_Batch *hair_geom = DRW_cache_particles_get_hair(psys, md);
DRWShadingGroup *shgrp = NULL;
Material *ma = give_current_material(ob, part->omat);
static float mat[4][4];
unit_m4(mat);
if (ma == NULL) {
ma = &defmaterial;
}
float *color_p = &ma->r;
float *metal_p = &ma->ray_mirror;
float *spec_p = &ma->spec;
float *rough_p = &ma->gloss_mir;
DRW_shgroup_call_add(stl->g_data->depth_shgrp, hair_geom, mat);
DRW_shgroup_call_add(stl->g_data->depth_shgrp_clip, hair_geom, mat);
shgrp = BLI_ghash_lookup(material_hash, (const void *)ma);
if (shgrp) {
DRW_shgroup_call_add(shgrp, hair_geom, mat);
}
else {
if (ma->use_nodes && ma->nodetree) {
struct GPUMaterial *gpumat = EEVEE_material_hair_get(scene, ma);
shgrp = DRW_shgroup_material_create(gpumat, psl->material_pass);
if (shgrp) {
add_standard_uniforms(shgrp, sldata, vedata, NULL, NULL, false);
BLI_ghash_insert(material_hash, ma, shgrp);
DRW_shgroup_call_add(shgrp, hair_geom, mat);
}
else {
/* Shader failed : pink color */
static float col[3] = {1.0f, 0.0f, 1.0f};
static float half = 0.5f;
color_p = col;
metal_p = spec_p = rough_p = &half;
}
}
/* Fallback to default shader */
if (shgrp == NULL) {
shgrp = EEVEE_default_shading_group_get(sldata, vedata, true, false, stl->effects->use_ssr);
DRW_shgroup_uniform_vec3(shgrp, "basecol", color_p, 1);
DRW_shgroup_uniform_float(shgrp, "metallic", metal_p, 1);
DRW_shgroup_uniform_float(shgrp, "specular", spec_p, 1);
DRW_shgroup_uniform_float(shgrp, "roughness", rough_p, 1);
BLI_ghash_insert(material_hash, ma, shgrp);
DRW_shgroup_call_add(shgrp, hair_geom, mat);
}
}
}
material_particle_hair(sldata, vedata, ob, psys, md);
}
else if (md->type == eModifierType_Hair) {
HairModifierData *hmd = (HairModifierData *)md;
for (HairGroup *group = hmd->hair->groups.first; group; group = group->next) {
material_hair(sldata, vedata, ob, group);
}
}
}
@@ -1306,6 +1487,8 @@ void EEVEE_materials_free(void)
MEM_SAFE_FREE(e_data.volume_shader_lib);
DRW_SHADER_FREE_SAFE(e_data.default_prepass_sh);
DRW_SHADER_FREE_SAFE(e_data.default_prepass_clip_sh);
DRW_SHADER_FREE_SAFE(e_data.default_prepass_hair_fiber_sh);
DRW_SHADER_FREE_SAFE(e_data.default_prepass_hair_fiber_clip_sh);
DRW_SHADER_FREE_SAFE(e_data.default_background);
DRW_TEXTURE_FREE_SAFE(e_data.util_tex);
}

View File

@@ -60,21 +60,22 @@ enum {
/* Material shader variations */
enum {
VAR_MAT_MESH = (1 << 0),
VAR_MAT_PROBE = (1 << 1),
VAR_MAT_HAIR = (1 << 2),
VAR_MAT_FLAT = (1 << 3),
VAR_MAT_BLEND = (1 << 4),
VAR_MAT_MESH = (1 << 0),
VAR_MAT_PROBE = (1 << 1),
VAR_MAT_HAIR = (1 << 2),
VAR_MAT_FLAT = (1 << 3),
VAR_MAT_BLEND = (1 << 4),
VAR_MAT_HAIR_FIBERS = (1 << 5),
/* Max number of variation */
/* IMPORTANT : Leave it last and set
* it's value accordingly. */
VAR_MAT_MAX = (1 << 5),
VAR_MAT_MAX = (1 << 6),
/* These are options that are not counted in VAR_MAT_MAX
* because they are not cumulative with the others above. */
VAR_MAT_CLIP = (1 << 8),
VAR_MAT_HASH = (1 << 9),
VAR_MAT_MULT = (1 << 10),
VAR_MAT_SHADOW = (1 << 11),
VAR_MAT_CLIP = (1 << 9),
VAR_MAT_HASH = (1 << 10),
VAR_MAT_MULT = (1 << 11),
VAR_MAT_SHADOW = (1 << 12),
VAR_MAT_REFRACT = (1 << 12),
};
@@ -476,6 +477,10 @@ typedef struct EEVEE_PrivateData {
struct DRWShadingGroup *depth_shgrp_cull;
struct DRWShadingGroup *depth_shgrp_clip;
struct DRWShadingGroup *depth_shgrp_clip_cull;
struct DRWShadingGroup *hair_fibers_depth_shgrp;
struct DRWShadingGroup *hair_fibers_depth_shgrp_cull;
struct DRWShadingGroup *hair_fibers_depth_shgrp_clip;
struct DRWShadingGroup *hair_fibers_depth_shgrp_clip_cull;
struct DRWShadingGroup *refract_depth_shgrp;
struct DRWShadingGroup *refract_depth_shgrp_cull;
struct DRWShadingGroup *refract_depth_shgrp_clip;
@@ -519,7 +524,7 @@ struct GPUMaterial *EEVEE_material_world_volume_get(
struct GPUMaterial *EEVEE_material_mesh_get(
struct Scene *scene, Material *ma, bool use_blend, bool use_multiply, bool use_refract);
struct GPUMaterial *EEVEE_material_mesh_depth_get(struct Scene *scene, Material *ma, bool use_hashed_alpha, bool is_shadow);
struct GPUMaterial *EEVEE_material_hair_get(struct Scene *scene, Material *ma);
struct GPUMaterial *EEVEE_material_hair_get(struct Scene *scene, Material *ma, bool use_fibers);
void EEVEE_materials_free(void);
void EEVEE_draw_default_passes(EEVEE_PassList *psl);

View File

@@ -0,0 +1,330 @@
#ifdef HAIR_SHADER_FIBERS
#define M_PI 3.1415926535897932384626433832795
mat4 translate(vec3 co)
{
return mat4(1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
co.x, co.y, co.z, 1.0);
}
mat4 rotateX(float angle)
{
float ca = cos(angle);
float sa = sin(angle);
return mat4(1.0, 0.0, 0.0, 0.0,
0.0, ca, sa, 0.0,
0.0, -sa, ca, 0.0,
0.0, 0.0, 0.0, 1.0);
}
mat4 rotateY(float angle)
{
float ca = cos(angle);
float sa = sin(angle);
return mat4(ca, 0.0, sa, 0.0,
0.0, 1.0, 0.0, 0.0,
-sa, 0.0, ca, 0.0,
0.0, 0.0, 0.0, 1.0);
}
mat4 rotateZ(float angle)
{
float ca = cos(angle);
float sa = sin(angle);
return mat4(ca, sa, 0.0, 0.0,
-sa, ca, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
}
/* Hair Displacement */
/* Note: The deformer functions below calculate a new location vector
* as well as a new direction (aka "normal"), using the partial derivatives of the transformation.
*
* Each transformation function can depend on the location L as well as the curve parameter t:
*
* Lnew = f(L, t)
* => dLnew/dt = del f/del L * dL/dt + del f/del t
*
* The first term is the Jacobian of the function f, dL/dt is the original direction vector.
* Some more information can be found here:
* https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch42.html
*/
/* Hairs tend to stick together and run in parallel.
* The effect increases with distance from the root,
* as the stresses pulling fibers apart decrease.
*/
struct ClumpParams
{
/* Relative strand thickness at the tip.
* (0.0, 1.0]
* 0.0 : Strand clumps into a single line
* 1.0 : Strand does not clump at all
* (> 1.0 is possible but not recommended)
*/
float thickness;
};
/* Hairs often don't have a circular cross section, but are somewhat flattened.
* This creates the local bending which results in the typical curly hair geometry.
*/
struct CurlParams
{
/* Radius of the curls.
* >= 0.0
*/
float radius;
/* Steepness of curls
* < 0.0 : Clockwise curls
* > 0.0 : Anti-clockwise curls
*/
float angle;
};
struct DeformParams
{
/* Strand tapering with distance from the root.
* < 1.0 : Taper is concave (recommended)
* = 1.0 : Taper is linear
* > 1.0 : Taper is convex (not recommended)
*/
float taper;
ClumpParams clump;
CurlParams curl;
};
void deform_taper(DeformParams params, float t, out float taper, out float dtaper)
{
taper = pow(t, params.taper);
dtaper = (t > 0.0) ? taper * params.taper / t : 0.0;
}
void deform_clump(DeformParams params,
float t, float tscale, mat4 target_matrix,
inout vec3 co, inout vec3 tang)
{
float taper, dtaper;
deform_taper(params, t, taper, dtaper);
float factor = (1.0 - params.clump.thickness) * taper;
float dfactor = (1.0 - params.clump.thickness) * dtaper;
vec3 target_co = target_matrix[3].xyz;
vec3 target_tang = target_matrix[0].xyz;
vec3 nco = co + (target_co - co) * factor;
vec3 ntang = normalize(tang + (target_tang - tang) * factor + (target_co - co) * dfactor);
co = nco;
tang = ntang;
}
void deform_curl(DeformParams params,
float t, float tscale,
inout mat4 target_matrix)
{
float pitch = 2.0*M_PI * params.curl.radius * tan(params.curl.angle);
float turns = tscale / (params.curl.radius * tan(params.curl.angle));
float angle = t * turns;
mat4 local_mat = rotateX(angle) * translate(vec3(0.0, params.curl.radius, 0.0)) * rotateY(params.curl.angle);
target_matrix = target_matrix * local_mat;
}
void deform_fiber(DeformParams params,
float t, float tscale, mat4 target_matrix,
inout vec3 loc, inout vec3 tang)
{
deform_curl(params, t, tscale, target_matrix);
deform_clump(params, t, tscale, target_matrix, loc, tang);
}
/*===================================*/
/* Hair Interpolation */
#define FIBER_RIBBON
uniform sampler2D fiber_data;
uniform int fiber_start;
uniform int strand_map_start;
uniform int strand_vertex_start;
uniform float ribbon_width;
uniform vec2 viewport_size;
#define INDEX_INVALID -1
vec2 read_texdata(int offset)
{
ivec2 offset2 = ivec2(offset % HAIR_SHADER_TEX_WIDTH, offset / HAIR_SHADER_TEX_WIDTH);
return texelFetch(fiber_data, offset2, 0).rg;
}
mat4 mat4_from_vectors(vec3 nor, vec3 tang, vec3 co)
{
tang = normalize(tang);
vec3 xnor = normalize(cross(nor, tang));
return mat4(vec4(tang, 0.0), vec4(xnor, 0.0), vec4(cross(tang, xnor), 0.0), vec4(co, 1.0));
}
void get_strand_data(int index, out int start, out int count)
{
int offset = strand_map_start + index;
vec2 a = read_texdata(offset);
start = floatBitsToInt(a.r);
count = floatBitsToInt(a.g);
}
void get_strand_vertex(int index, out vec3 co, out vec3 nor, out vec3 tang)
{
int offset = strand_vertex_start + index * 5;
vec2 a = read_texdata(offset);
vec2 b = read_texdata(offset + 1);
vec2 c = read_texdata(offset + 2);
vec2 d = read_texdata(offset + 3);
vec2 e = read_texdata(offset + 4);
co = vec3(a.rg, b.r);
nor = vec3(b.g, c.rg);
tang = vec3(d.rg, e.r);
}
void get_strand_root(int index, out vec3 co)
{
int offset = strand_vertex_start + index * 5;
vec2 a = read_texdata(offset);
vec2 b = read_texdata(offset + 1);
co = vec3(a.rg, b.r);
}
void get_fiber_data(int fiber_index, out ivec4 parent_index, out vec4 parent_weight, out vec3 pos)
{
int offset = fiber_start + fiber_index * 6;
vec2 a = read_texdata(offset);
vec2 b = read_texdata(offset + 1);
vec2 c = read_texdata(offset + 2);
vec2 d = read_texdata(offset + 3);
vec2 e = read_texdata(offset + 4);
vec2 f = read_texdata(offset + 5);
parent_index = ivec4(floatBitsToInt(a.rg), floatBitsToInt(b.rg));
parent_weight = vec4(c.rg, d.rg);
pos = vec3(e.rg, f.r);
}
void interpolate_parent_curve_full(int index, float curve_param, out vec3 co, out vec3 nor, out vec3 tang, out vec3 rootco)
{
int start, count;
get_strand_data(index, start, count);
get_strand_root(start, rootco);
#if 0 // Don't have to worry about out-of-bounds segment here, as long as lerpfac becomes 0.0 when curve_param==1.0
float maxlen = float(count - 1);
float arclength = curve_param * maxlen;
int segment = min(int(arclength), count - 2);
float lerpfac = arclength - min(floor(arclength), maxlen - 1.0);
#else
float maxlen = float(count - 1);
float arclength = curve_param * maxlen;
int segment = int(arclength);
float lerpfac = arclength - floor(arclength);
#endif
vec3 co0, nor0, tang0;
vec3 co1, nor1, tang1;
get_strand_vertex(start + segment, co0, nor0, tang0);
get_strand_vertex(start + segment + 1, co1, nor1, tang1);
co = mix(co0, co1, lerpfac) - rootco;
nor = mix(nor0, nor1, lerpfac);
tang = mix(tang0, tang1, lerpfac);
}
void interpolate_parent_curve(int index, float curve_param, out vec3 co, out vec3 tang)
{
vec3 nor;
vec3 rootco;
interpolate_parent_curve_full(index, curve_param, co, nor, tang, rootco);
}
void interpolate_vertex(int fiber_index, float curve_param,
out vec3 co, out vec3 tang,
out mat4 target_matrix)
{
co = vec3(0.0);
tang = vec3(0.0);
target_matrix = mat4(1.0);
ivec4 parent_index;
vec4 parent_weight;
vec3 rootco;
get_fiber_data(fiber_index, parent_index, parent_weight, rootco);
if (parent_index.x != INDEX_INVALID) {
vec3 pco, pnor, ptang, prootco;
interpolate_parent_curve_full(parent_index.x, curve_param, pco, pnor, ptang, prootco);
co += parent_weight.x * pco;
tang += parent_weight.x * normalize(ptang);
target_matrix = mat4_from_vectors(pnor, ptang, pco + prootco);
}
if (parent_index.y != INDEX_INVALID) {
vec3 pco, ptang;
interpolate_parent_curve(parent_index.y, curve_param, pco, ptang);
co += parent_weight.y * pco;
tang += parent_weight.y * normalize(ptang);
}
if (parent_index.z != INDEX_INVALID) {
vec3 pco, ptang;
interpolate_parent_curve(parent_index.z, curve_param, pco, ptang);
co += parent_weight.z * pco;
tang += parent_weight.z * normalize(ptang);
}
if (parent_index.w != INDEX_INVALID) {
vec3 pco, ptang;
interpolate_parent_curve(parent_index.w, curve_param, pco, ptang);
co += parent_weight.w * pco;
tang += parent_weight.w * normalize(ptang);
}
co += rootco;
tang = normalize(tang);
}
void hair_fiber_get_vertex(int fiber_index, float curve_param, mat4 ModelViewMatrix, out vec3 pos, out vec3 nor, out vec2 view_offset)
{
vec3 target_loc;
mat4 target_matrix;
interpolate_vertex(fiber_index, curve_param, pos, nor, target_matrix);
DeformParams deform_params;
deform_params.taper = 2.0;
deform_params.clump.thickness = 0.15;
deform_params.curl.radius = 0.1;
deform_params.curl.angle = 0.2;
// TODO define proper curve scale, independent of subdivision!
//deform_fiber(deform_params, curve_param, 1.0, target_matrix, pos, nor);
#ifdef FIBER_RIBBON
float ribbon_side = (float(gl_VertexID % 2) - 0.5) * ribbon_width;
{
vec4 view_nor = ModelViewMatrix * vec4(nor, 0.0);
view_offset = vec2(view_nor.y, -view_nor.x);
float L = length(view_offset);
if (L > 0.0) {
view_offset *= ribbon_side / (L * viewport_size);
}
}
#else
view_offset = vec2(0.0);
#endif
}
#endif /*HAIR_SHADER_FIBERS*/

View File

@@ -7,8 +7,13 @@ uniform mat3 WorldNormalMatrix;
uniform mat3 NormalMatrix;
#endif
#ifndef HAIR_SHADER_FIBERS
in vec3 pos;
in vec3 nor;
#else
in int fiber_index;
in float curve_param;
#endif
out vec3 worldPosition;
out vec3 viewPosition;
@@ -25,7 +30,17 @@ out vec3 viewNormal;
#endif
void main() {
#ifndef HAIR_SHADER_FIBERS
gl_Position = ModelViewProjectionMatrix * vec4(pos, 1.0);
#else
vec3 pos;
vec3 nor;
vec2 view_offset;
hair_fiber_get_vertex(fiber_index, curve_param, ModelViewMatrix, pos, nor, view_offset);
gl_Position = ModelViewProjectionMatrix * vec4(pos, 1.0);
gl_Position.xy += view_offset * gl_Position.w;
#endif
viewPosition = (ModelViewMatrix * vec4(pos, 1.0)).xyz;
worldPosition = (ModelMatrix * vec4(pos, 1.0)).xyz;
viewNormal = normalize(NormalMatrix * nor);
@@ -37,4 +52,4 @@ void main() {
#ifdef ATTRIB
pass_attrib(pos);
#endif
}
}

View File

@@ -1,15 +1,31 @@
uniform mat4 ModelViewProjectionMatrix;
uniform mat4 ModelMatrix;
uniform mat4 ModelViewMatrix;
#ifdef CLIP_PLANES
uniform vec4 ClipPlanes[1];
#endif
#ifndef HAIR_SHADER_FIBERS
in vec3 pos;
#else
in int fiber_index;
in float curve_param;
#endif
void main()
{
#ifndef HAIR_SHADER_FIBERS
gl_Position = ModelViewProjectionMatrix * vec4(pos, 1.0);
#else
vec3 pos;
vec3 nor;
vec2 view_offset;
hair_fiber_get_vertex(fiber_index, curve_param, ModelViewMatrix, pos, nor, view_offset);
gl_Position = ModelViewProjectionMatrix * vec4(pos, 1.0);
gl_Position.xy += view_offset * gl_Position.w;
#endif
#ifdef CLIP_PLANES
vec4 worldPosition = (ModelMatrix * vec4(pos, 1.0));
gl_ClipDistance[0] = dot(worldPosition, ClipPlanes[0]);

View File

@@ -2543,3 +2543,38 @@ Gwn_Batch *DRW_cache_particles_get_prim(int type)
return NULL;
}
/* -------------------------------------------------------------------- */
/** \name Strands
* \{ */
Gwn_Batch *DRW_cache_editstrands_get_tips(struct BMEditStrands *es)
{
return DRW_editstrands_batch_cache_get_tips(es);
}
Gwn_Batch *DRW_cache_editstrands_get_roots(struct BMEditStrands *es)
{
return DRW_editstrands_batch_cache_get_roots(es);
}
Gwn_Batch *DRW_cache_editstrands_get_points(struct BMEditStrands *es)
{
return DRW_editstrands_batch_cache_get_points(es);
}
Gwn_Batch *DRW_cache_editstrands_get_wires(struct BMEditStrands *es)
{
return DRW_editstrands_batch_cache_get_wires(es);
}
/* -------------------------------------------------------------------- */
/** \name Hair */
Gwn_Batch *DRW_cache_hair_get_fibers(struct HairGroup *group, int subdiv, struct DerivedMesh *scalp,
const struct DRWHairFiberTextureBuffer **r_buffer)
{
return DRW_hair_batch_cache_get_fibers(group, subdiv, scalp, r_buffer);
}

View File

@@ -30,6 +30,10 @@ struct Gwn_Batch;
struct GPUMaterial;
struct Object;
struct ModifierData;
struct BMEditStrands;
struct HairGroup;
struct DRWHairFiberTextureBuffer;
struct DerivedMesh;
void DRW_shape_cache_free(void);
@@ -156,4 +160,14 @@ struct Gwn_Batch *DRW_cache_particles_get_hair(struct ParticleSystem *psys, stru
struct Gwn_Batch *DRW_cache_particles_get_dots(struct ParticleSystem *psys);
struct Gwn_Batch *DRW_cache_particles_get_prim(int type);
/* Strands */
struct Gwn_Batch *DRW_cache_editstrands_get_tips(struct BMEditStrands *es);
struct Gwn_Batch *DRW_cache_editstrands_get_roots(struct BMEditStrands *es);
struct Gwn_Batch *DRW_cache_editstrands_get_points(struct BMEditStrands *es);
struct Gwn_Batch *DRW_cache_editstrands_get_wires(struct BMEditStrands *es);
/* Hair */
struct Gwn_Batch *DRW_cache_hair_get_fibers(struct HairGroup *group, int subdiv, struct DerivedMesh *scalp,
const struct DRWHairFiberTextureBuffer **r_buffer);
#endif /* __DRAW_CACHE_H__ */

View File

@@ -32,6 +32,10 @@ struct ListBase;
struct CurveCache;
struct ParticleSystem;
struct ModifierData;
struct BMEditStrands;
struct HairGroup;
struct DRWHairFiberTextureBuffer;
struct DerivedMesh;
struct Curve;
struct Lattice;
@@ -50,6 +54,12 @@ void DRW_lattice_batch_cache_free(struct Lattice *lt);
void DRW_particle_batch_cache_dirty(struct ParticleSystem *psys, int mode);
void DRW_particle_batch_cache_free(struct ParticleSystem *psys);
void DRW_hair_batch_cache_dirty(struct HairGroup *group, int mode);
void DRW_hair_batch_cache_free(struct HairGroup *group);
void DRW_editstrands_batch_cache_dirty(struct BMEditStrands *es, int mode);
void DRW_editstrands_batch_cache_free(struct BMEditStrands *es);
/* Curve */
struct Gwn_Batch *DRW_curve_batch_cache_get_wire_edge(struct Curve *cu, struct CurveCache *ob_curve_cache);
struct Gwn_Batch *DRW_curve_batch_cache_get_normal_edge(
@@ -107,4 +117,14 @@ void DRW_mesh_cache_sculpt_coords_ensure(struct Mesh *me);
struct Gwn_Batch *DRW_particles_batch_cache_get_hair(struct ParticleSystem *psys, struct ModifierData *md);
struct Gwn_Batch *DRW_particles_batch_cache_get_dots(struct ParticleSystem *psys);
/* Strands */
struct Gwn_Batch *DRW_editstrands_batch_cache_get_wires(struct BMEditStrands *es);
struct Gwn_Batch *DRW_editstrands_batch_cache_get_tips(struct BMEditStrands *es);
struct Gwn_Batch *DRW_editstrands_batch_cache_get_roots(struct BMEditStrands *es);
struct Gwn_Batch *DRW_editstrands_batch_cache_get_points(struct BMEditStrands *es);
/* Hair */
struct Gwn_Batch *DRW_hair_batch_cache_get_fibers(struct HairGroup *group, int subdiv, struct DerivedMesh *scalp,
const struct DRWHairFiberTextureBuffer **r_buffer);
#endif /* __DRAW_CACHE_IMPL_H__ */

View File

@@ -0,0 +1,310 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2017 by Blender Foundation.
* All rights reserved.
*
* Contributor(s): Blender Foundation, Mike Erwin, Dalai Felinto
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file draw_cache_impl_strands.c
* \ingroup draw
*
* \brief Strands API for render engines
*/
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
#include "BLI_math_vector.h"
#include "BLI_ghash.h"
#include "DNA_hair_types.h"
#include "DNA_scene_types.h"
#include "BKE_hair.h"
#include "GPU_batch.h"
#include "GPU_extensions.h"
#include "GPU_texture.h"
#include "draw_common.h"
#include "draw_cache_impl.h" /* own include */
#include "DRW_render.h"
// timing
//#define DEBUG_TIME
#ifdef DEBUG_TIME
# include "PIL_time_utildefines.h"
#else
# define TIMEIT_START(var)
# define TIMEIT_VALUE(var)
# define TIMEIT_VALUE_PRINT(var)
# define TIMEIT_END(var)
# define TIMEIT_BENCH(expr, id) (expr)
# define TIMEIT_BLOCK_INIT(var)
# define TIMEIT_BLOCK_START(var)
# define TIMEIT_BLOCK_END(var)
# define TIMEIT_BLOCK_STATS(var)
#endif
/* ---------------------------------------------------------------------- */
/* Hair Gwn_Batch Cache */
typedef struct HairBatchCache {
Gwn_VertBuf *verts;
Gwn_IndexBuf *segments;
Gwn_Batch *fibers;
DRWHairFiberTextureBuffer texbuffer;
/* settings to determine if cache is invalid */
bool is_dirty;
} HairBatchCache;
/* Gwn_Batch cache management. */
static void hair_batch_cache_clear(HairGroup *group);
static bool hair_batch_cache_valid(HairGroup *group)
{
HairBatchCache *cache = group->draw_batch_cache;
if (cache == NULL) {
return false;
}
if (cache->is_dirty) {
return false;
}
return true;
}
static void hair_batch_cache_init(HairGroup *group)
{
HairBatchCache *cache = group->draw_batch_cache;
if (!cache) {
cache = group->draw_batch_cache = MEM_callocN(sizeof(*cache), __func__);
}
else {
memset(cache, 0, sizeof(*cache));
}
cache->is_dirty = false;
}
static HairBatchCache *hair_batch_cache_get(HairGroup *group)
{
if (!hair_batch_cache_valid(group)) {
hair_batch_cache_clear(group);
hair_batch_cache_init(group);
}
return group->draw_batch_cache;
}
void DRW_hair_batch_cache_dirty(HairGroup *group, int mode)
{
HairBatchCache *cache = group->draw_batch_cache;
if (cache == NULL) {
return;
}
switch (mode) {
case BKE_HAIR_BATCH_DIRTY_ALL:
cache->is_dirty = true;
break;
default:
BLI_assert(0);
}
}
static void hair_batch_cache_clear(HairGroup *group)
{
HairBatchCache *cache = group->draw_batch_cache;
if (group->draw_texture_cache) {
GPU_texture_free(group->draw_texture_cache);
group->draw_texture_cache = NULL;
}
if (cache) {
GWN_BATCH_DISCARD_SAFE(cache->fibers);
GWN_VERTBUF_DISCARD_SAFE(cache->verts);
GWN_INDEXBUF_DISCARD_SAFE(cache->segments);
{
DRWHairFiberTextureBuffer *buffer = &cache->texbuffer;
if (buffer->data) {
MEM_freeN(buffer->data);
buffer->data = NULL;
}
buffer->fiber_start = 0;
buffer->strand_map_start = 0;
buffer->strand_vertex_start = 0;
buffer->width = 0;
buffer->height = 0;
}
}
}
void DRW_hair_batch_cache_free(HairGroup *group)
{
hair_batch_cache_clear(group);
MEM_SAFE_FREE(group->draw_batch_cache);
}
static void hair_batch_cache_ensure_fibers(HairGroup *group, struct DerivedMesh *scalp, int subdiv, HairBatchCache *cache)
{
TIMEIT_START(hair_batch_cache_ensure_fibers);
GWN_VERTBUF_DISCARD_SAFE(cache->verts);
GWN_INDEXBUF_DISCARD_SAFE(cache->segments);
const int totfibers = group->num_follicles;
int *fiber_lengths = BKE_hair_group_get_fiber_lengths(group, scalp, subdiv);
int totpoint = 0;
for (int i = 0; i < totfibers; ++i) {
totpoint += fiber_lengths[i];
}
const int totseg = totpoint - totfibers;
static Gwn_VertFormat format = { 0 };
static unsigned curve_param_id, fiber_index_id;
/* initialize vertex format */
if (format.attrib_ct == 0) {
fiber_index_id = GWN_vertformat_attr_add(&format, "fiber_index", GWN_COMP_I32, 1, GWN_FETCH_INT);
curve_param_id = GWN_vertformat_attr_add(&format, "curve_param", GWN_COMP_F32, 1, GWN_FETCH_FLOAT);
}
cache->verts = GWN_vertbuf_create_with_format(&format);
Gwn_IndexBufBuilder elb;
{
TIMEIT_START(data_alloc);
Gwn_PrimType prim_type;
unsigned prim_ct, vert_ct;
prim_type = GWN_PRIM_TRIS;
prim_ct = 2 * totseg;
vert_ct = 2 * totpoint;
GWN_vertbuf_data_alloc(cache->verts, vert_ct);
GWN_indexbuf_init(&elb, prim_type, prim_ct, vert_ct);
TIMEIT_END(data_alloc);
}
TIMEIT_START(data_fill);
TIMEIT_BLOCK_INIT(GWN_vertbuf_attr_set);
TIMEIT_BLOCK_INIT(GWN_indexbuf_add_tri_verts);
int vi = 0;
for (int i = 0; i < totfibers; ++i) {
const int fiblen = fiber_lengths[i];
const float da = fiblen > 1 ? 1.0f / (fiblen-1) : 0.0f;
float a = 0.0f;
for (int k = 0; k < fiblen; ++k) {
TIMEIT_BLOCK_START(GWN_vertbuf_attr_set);
GWN_vertbuf_attr_set(cache->verts, fiber_index_id, vi, &i);
GWN_vertbuf_attr_set(cache->verts, curve_param_id, vi, &a);
GWN_vertbuf_attr_set(cache->verts, fiber_index_id, vi+1, &i);
GWN_vertbuf_attr_set(cache->verts, curve_param_id, vi+1, &a);
TIMEIT_BLOCK_END(GWN_vertbuf_attr_set);
if (k > 0) {
TIMEIT_BLOCK_START(GWN_indexbuf_add_tri_verts);
GWN_indexbuf_add_tri_verts(&elb, vi-2, vi-1, vi+1);
GWN_indexbuf_add_tri_verts(&elb, vi+1, vi, vi-2);
TIMEIT_BLOCK_END(GWN_indexbuf_add_tri_verts);
}
vi += 2;
a += da;
}
}
TIMEIT_BLOCK_STATS(GWN_vertbuf_attr_set);
TIMEIT_BLOCK_STATS(GWN_indexbuf_add_tri_verts);
#ifdef DEBUG_TIME
printf("Total GWN time: %f\n", _timeit_var_GWN_vertbuf_attr_set + _timeit_var_GWN_indexbuf_add_tri_verts);
#endif
fflush(stdout);
TIMEIT_END(data_fill);
MEM_freeN(fiber_lengths);
TIMEIT_BENCH(cache->segments = GWN_indexbuf_build(&elb), indexbuf_build);
TIMEIT_END(hair_batch_cache_ensure_fibers);
}
static void hair_batch_cache_ensure_texbuffer(HairGroup *group, struct DerivedMesh *scalp, int subdiv, HairBatchCache *cache)
{
DRWHairFiberTextureBuffer *buffer = &cache->texbuffer;
static const int elemsize = 8;
const int width = GPU_max_texture_size();
const int align = width * elemsize;
// Offsets in bytes
int b_size, b_strand_map_start, b_strand_vertex_start, b_fiber_start;
BKE_hair_group_get_texture_buffer_size(group, scalp, subdiv, &b_size,
&b_strand_map_start, &b_strand_vertex_start, &b_fiber_start);
// Pad for alignment
b_size += align - b_size % align;
// Convert to element size as texture offsets
const int size = b_size / elemsize;
const int height = size / width;
buffer->data = MEM_mallocN(b_size, "hair fiber texture buffer");
BKE_hair_group_get_texture_buffer(group, scalp, subdiv, buffer->data);
buffer->width = width;
buffer->height = height;
buffer->strand_map_start = b_strand_map_start / elemsize;
buffer->strand_vertex_start = b_strand_vertex_start / elemsize;
buffer->fiber_start = b_fiber_start / elemsize;
}
Gwn_Batch *DRW_hair_batch_cache_get_fibers(HairGroup *group, int subdiv, struct DerivedMesh *scalp,
const DRWHairFiberTextureBuffer **r_buffer)
{
HairBatchCache *cache = hair_batch_cache_get(group);
TIMEIT_START(DRW_hair_batch_cache_get_fibers);
if (cache->fibers == NULL) {
TIMEIT_BENCH(hair_batch_cache_ensure_fibers(group, scalp, subdiv, cache),
hair_batch_cache_ensure_fibers);
TIMEIT_BENCH(cache->fibers = GWN_batch_create(GWN_PRIM_TRIS, cache->verts, cache->segments),
GWN_batch_create);
TIMEIT_BENCH(hair_batch_cache_ensure_texbuffer(group, scalp, subdiv, cache),
hair_batch_cache_ensure_texbuffer);
}
if (r_buffer) {
*r_buffer = &cache->texbuffer;
}
TIMEIT_END(DRW_hair_batch_cache_get_fibers);
return cache->fibers;
}

View File

@@ -0,0 +1,341 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2017 by Blender Foundation.
* All rights reserved.
*
* Contributor(s): Blender Foundation, Mike Erwin, Dalai Felinto
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file draw_cache_impl_strands.c
* \ingroup draw
*
* \brief Strands API for render engines
*/
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
#include "BLI_math_vector.h"
#include "BLI_ghash.h"
#include "DNA_hair_types.h"
#include "DNA_scene_types.h"
#include "BKE_editstrands.h"
#include "GPU_batch.h"
#include "GPU_extensions.h"
#include "GPU_texture.h"
#include "draw_common.h"
#include "draw_cache_impl.h" /* own include */
#include "DRW_render.h"
// timing
//#define DEBUG_TIME
#ifdef DEBUG_TIME
# include "PIL_time_utildefines.h"
#else
# define TIMEIT_START(var)
# define TIMEIT_VALUE(var)
# define TIMEIT_VALUE_PRINT(var)
# define TIMEIT_END(var)
# define TIMEIT_BENCH(expr, id) (expr)
# define TIMEIT_BLOCK_INIT(var)
# define TIMEIT_BLOCK_START(var)
# define TIMEIT_BLOCK_END(var)
# define TIMEIT_BLOCK_STATS(var)
#endif
/* ---------------------------------------------------------------------- */
/* Strands Gwn_Batch Cache */
typedef enum VertexDrawFlags
{
STRANDS_VERTEX_SELECT = (1 << 0),
} VertexDrawFlags;
typedef struct StrandsBatchCache {
Gwn_VertBuf *pos;
Gwn_IndexBuf *segments;
Gwn_IndexBuf *tips_idx;
Gwn_IndexBuf *roots_idx;
Gwn_Batch *wires;
Gwn_Batch *tips;
Gwn_Batch *roots;
Gwn_Batch *points;
int segment_count;
int point_count;
/* settings to determine if cache is invalid */
bool is_dirty;
} StrandsBatchCache;
/* Gwn_Batch cache management. */
static void editstrands_batch_cache_clear(BMEditStrands *es);
static bool editstrands_batch_cache_valid(BMEditStrands *es)
{
StrandsBatchCache *cache = es->batch_cache;
if (cache == NULL) {
return false;
}
if (cache->is_dirty) {
return false;
}
return true;
}
static void editstrands_batch_cache_init(BMEditStrands *es)
{
StrandsBatchCache *cache = es->batch_cache;
if (!cache) {
cache = es->batch_cache = MEM_callocN(sizeof(*cache), __func__);
}
else {
memset(cache, 0, sizeof(*cache));
}
cache->is_dirty = false;
}
static StrandsBatchCache *editstrands_batch_cache_get(BMEditStrands *es)
{
if (!editstrands_batch_cache_valid(es)) {
editstrands_batch_cache_clear(es);
editstrands_batch_cache_init(es);
}
return es->batch_cache;
}
void DRW_editstrands_batch_cache_dirty(BMEditStrands *es, int mode)
{
StrandsBatchCache *cache = es->batch_cache;
if (cache == NULL) {
return;
}
switch (mode) {
case BKE_STRANDS_BATCH_DIRTY_ALL:
case BKE_STRANDS_BATCH_DIRTY_SELECT:
cache->is_dirty = true;
break;
default:
BLI_assert(0);
}
}
static void editstrands_batch_cache_clear(BMEditStrands *es)
{
StrandsBatchCache *cache = es->batch_cache;
if (cache) {
GWN_BATCH_DISCARD_SAFE(cache->wires);
GWN_BATCH_DISCARD_SAFE(cache->points);
GWN_BATCH_DISCARD_SAFE(cache->tips);
GWN_BATCH_DISCARD_SAFE(cache->roots);
GWN_VERTBUF_DISCARD_SAFE(cache->pos);
GWN_INDEXBUF_DISCARD_SAFE(cache->segments);
}
}
void DRW_editstrands_batch_cache_free(BMEditStrands *es)
{
editstrands_batch_cache_clear(es);
MEM_SAFE_FREE(es->batch_cache);
}
static void editstrands_batch_cache_ensure_pos(BMEditStrands *es, StrandsBatchCache *cache)
{
if (cache->pos) {
return;
}
GWN_VERTBUF_DISCARD_SAFE(cache->pos);
static Gwn_VertFormat format = { 0 };
static unsigned pos_id, flag_id;
/* initialize vertex format */
if (format.attrib_ct == 0) {
pos_id = GWN_vertformat_attr_add(&format, "pos", GWN_COMP_F32, 3, GWN_FETCH_FLOAT);
flag_id = GWN_vertformat_attr_add(&format, "flag", GWN_COMP_U8, 1, GWN_FETCH_INT);
}
BMesh *bm = es->base.bm;
BMVert *vert;
BMIter iter;
int curr_point;
cache->pos = GWN_vertbuf_create_with_format(&format);
GWN_vertbuf_data_alloc(cache->pos, bm->totvert);
BM_ITER_MESH_INDEX(vert, &iter, bm, BM_VERTS_OF_MESH, curr_point) {
GWN_vertbuf_attr_set(cache->pos, pos_id, curr_point, vert->co);
uint8_t flag = 0;
if (BM_elem_flag_test(vert, BM_ELEM_SELECT))
flag |= STRANDS_VERTEX_SELECT;
GWN_vertbuf_attr_set(cache->pos, flag_id, curr_point, &flag);
}
}
static void editstrands_batch_cache_ensure_segments(BMEditStrands *es, StrandsBatchCache *cache)
{
if (cache->segments) {
return;
}
GWN_INDEXBUF_DISCARD_SAFE(cache->segments);
BMesh *bm = es->base.bm;
BMEdge *edge;
BMIter iter;
Gwn_IndexBufBuilder elb;
GWN_indexbuf_init(&elb, GWN_PRIM_LINES, bm->totedge, bm->totvert);
BM_mesh_elem_index_ensure(bm, BM_VERT);
BM_ITER_MESH(edge, &iter, bm, BM_EDGES_OF_MESH) {
GWN_indexbuf_add_line_verts(&elb, BM_elem_index_get(edge->v1), BM_elem_index_get(edge->v2));
}
cache->segments = GWN_indexbuf_build(&elb);
}
static void editstrands_batch_cache_ensure_tips_idx(BMEditStrands *es, StrandsBatchCache *cache)
{
if (cache->tips_idx) {
return;
}
GWN_INDEXBUF_DISCARD_SAFE(cache->tips_idx);
BMesh *bm = es->base.bm;
int totstrands = BM_strands_count(bm);
BMVert *root, *vert;
BMIter iter, iter_strand;
Gwn_IndexBufBuilder elb;
GWN_indexbuf_init(&elb, GWN_PRIM_POINTS, totstrands, bm->totvert);
BM_mesh_elem_index_ensure(bm, BM_VERT);
BM_ITER_STRANDS(root, &iter, bm, BM_STRANDS_OF_MESH) {
BM_ITER_STRANDS_ELEM(vert, &iter_strand, root, BM_VERTS_OF_STRAND)
{
if (BM_strands_vert_is_tip(vert)) {
GWN_indexbuf_add_point_vert(&elb, BM_elem_index_get(vert));
break;
}
}
}
cache->tips_idx = GWN_indexbuf_build(&elb);
}
static void editstrands_batch_cache_ensure_roots_idx(BMEditStrands *es, StrandsBatchCache *cache)
{
if (cache->roots_idx) {
return;
}
GWN_INDEXBUF_DISCARD_SAFE(cache->roots_idx);
BMesh *bm = es->base.bm;
int totstrands = BM_strands_count(bm);
BMVert *root, *vert;
BMIter iter, iter_strand;
Gwn_IndexBufBuilder elb;
GWN_indexbuf_init(&elb, GWN_PRIM_POINTS, totstrands, bm->totvert);
BM_mesh_elem_index_ensure(bm, BM_VERT);
BM_ITER_STRANDS(root, &iter, bm, BM_STRANDS_OF_MESH) {
BM_ITER_STRANDS_ELEM(vert, &iter_strand, root, BM_VERTS_OF_STRAND)
{
if (BM_strands_vert_is_root(vert)) {
GWN_indexbuf_add_point_vert(&elb, BM_elem_index_get(vert));
break;
}
}
}
cache->roots_idx = GWN_indexbuf_build(&elb);
}
Gwn_Batch *DRW_editstrands_batch_cache_get_wires(BMEditStrands *es)
{
StrandsBatchCache *cache = editstrands_batch_cache_get(es);
if (cache->wires == NULL) {
editstrands_batch_cache_ensure_pos(es, cache);
editstrands_batch_cache_ensure_segments(es, cache);
cache->wires = GWN_batch_create(GWN_PRIM_LINES, cache->pos, cache->segments);
}
return cache->wires;
}
Gwn_Batch *DRW_editstrands_batch_cache_get_tips(BMEditStrands *es)
{
StrandsBatchCache *cache = editstrands_batch_cache_get(es);
if (cache->tips == NULL) {
editstrands_batch_cache_ensure_pos(es, cache);
editstrands_batch_cache_ensure_tips_idx(es, cache);
cache->tips = GWN_batch_create(GWN_PRIM_POINTS, cache->pos, cache->tips_idx);
}
return cache->tips;
}
Gwn_Batch *DRW_editstrands_batch_cache_get_roots(BMEditStrands *es)
{
StrandsBatchCache *cache = editstrands_batch_cache_get(es);
if (cache->roots == NULL) {
editstrands_batch_cache_ensure_pos(es, cache);
editstrands_batch_cache_ensure_roots_idx(es, cache);
cache->roots = GWN_batch_create(GWN_PRIM_POINTS, cache->pos, cache->roots_idx);
}
return cache->roots;
}
Gwn_Batch *DRW_editstrands_batch_cache_get_points(BMEditStrands *es)
{
StrandsBatchCache *cache = editstrands_batch_cache_get(es);
if (cache->points == NULL) {
editstrands_batch_cache_ensure_pos(es, cache);
cache->points = GWN_batch_create(GWN_PRIM_POINTS, cache->pos, NULL);
}
return cache->points;
}

View File

@@ -29,7 +29,9 @@
struct DRWPass;
struct DRWShadingGroup;
struct Gwn_Batch;
struct GPUTexture;
struct Object;
struct Scene;
struct SceneLayer;
/* Used as ubo but colors can be directly referenced as well */
@@ -128,4 +130,19 @@ void DRW_shgroup_armature_edit(
bool DRW_pose_mode_armature(
struct Object *ob, struct Object *active_ob);
/* hair drawing */
typedef struct DRWHairFiberTextureBuffer {
void *data;
int strand_map_start;
int strand_vertex_start;
int fiber_start;
int width;
int height;
} DRWHairFiberTextureBuffer;
const char* DRW_hair_shader_defines(void);
void DRW_hair_shader_uniforms(struct DRWShadingGroup *shgrp, struct Scene *scene,
struct GPUTexture **fibertex, const struct DRWHairFiberTextureBuffer *texbuffer);
#endif /* __DRAW_COMMON_H__ */

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2016, Blender Foundation.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Contributor(s): Blender Institute
*
*/
/** \file draw_hair.c
* \ingroup draw
*/
#include "DNA_scene_types.h"
#include "DRW_render.h"
#include "BLI_utildefines.h"
#include "GPU_extensions.h"
#include "GPU_texture.h"
#include "draw_common.h"
const char* DRW_hair_shader_defines(void)
{
static char str[256];
BLI_snprintf(str, sizeof(str), "#define HAIR_SHADER_FIBERS\n#define HAIR_SHADER_TEX_WIDTH %d\n",
GPU_max_texture_size());
return str;
}
void DRW_hair_shader_uniforms(DRWShadingGroup *shgrp, Scene *scene,
GPUTexture **fibertex, const DRWHairFiberTextureBuffer *texbuffer)
{
const HairEditSettings *tsettings = &scene->toolsettings->hair_edit;
DRW_shgroup_uniform_vec2(shgrp, "viewport_size", DRW_viewport_size_get(), 1);
//DRW_shgroup_uniform_float(shgrp, "ribbon_width", &tsettings->hair_draw_size, 1);
static float test = 2.5f;
DRW_shgroup_uniform_float(shgrp, "ribbon_width", &test, 1);
DRW_shgroup_uniform_buffer(shgrp, "fiber_data", fibertex);
DRW_shgroup_uniform_int(shgrp, "strand_map_start", &texbuffer->strand_map_start, 1);
DRW_shgroup_uniform_int(shgrp, "strand_vertex_start", &texbuffer->strand_vertex_start, 1);
DRW_shgroup_uniform_int(shgrp, "fiber_start", &texbuffer->fiber_start, 1);
}

View File

@@ -2917,6 +2917,9 @@ static void DRW_engines_enable_from_mode(int mode)
case CTX_MODE_PARTICLE:
use_drw_engine(&draw_engine_particle_type);
break;
case CTX_MODE_HAIR:
use_drw_engine(&draw_engine_edit_strands_type);
break;
case CTX_MODE_OBJECT:
break;
default:
@@ -3579,6 +3582,7 @@ void DRW_engines_register(void)
DRW_engine_register(&draw_engine_edit_lattice_type);
DRW_engine_register(&draw_engine_edit_mesh_type);
DRW_engine_register(&draw_engine_edit_metaball_type);
DRW_engine_register(&draw_engine_edit_strands_type);
DRW_engine_register(&draw_engine_edit_surface_type);
DRW_engine_register(&draw_engine_edit_text_type);
DRW_engine_register(&draw_engine_paint_texture_type);
@@ -3602,6 +3606,12 @@ void DRW_engines_register(void)
/* BKE: particle.c */
extern void *BKE_particle_batch_cache_dirty_cb;
extern void *BKE_particle_batch_cache_free_cb;
/* BKE: editstrands.c */
extern void *BKE_editstrands_batch_cache_dirty_cb;
extern void *BKE_editstrands_batch_cache_free_cb;
/* BKE: hair.c */
extern void *BKE_hair_batch_cache_dirty_cb;
extern void *BKE_hair_batch_cache_free_cb;
BKE_curve_batch_cache_dirty_cb = DRW_curve_batch_cache_dirty;
BKE_curve_batch_cache_free_cb = DRW_curve_batch_cache_free;
@@ -3614,6 +3624,12 @@ void DRW_engines_register(void)
BKE_particle_batch_cache_dirty_cb = DRW_particle_batch_cache_dirty;
BKE_particle_batch_cache_free_cb = DRW_particle_batch_cache_free;
BKE_editstrands_batch_cache_dirty_cb = DRW_editstrands_batch_cache_dirty;
BKE_editstrands_batch_cache_free_cb = DRW_editstrands_batch_cache_free;
BKE_hair_batch_cache_dirty_cb = DRW_hair_batch_cache_dirty;
BKE_hair_batch_cache_free_cb = DRW_hair_batch_cache_free;
}
}

View File

@@ -32,6 +32,7 @@ extern DrawEngineType draw_engine_edit_curve_type;
extern DrawEngineType draw_engine_edit_lattice_type;
extern DrawEngineType draw_engine_edit_mesh_type;
extern DrawEngineType draw_engine_edit_metaball_type;
extern DrawEngineType draw_engine_edit_strands_type;
extern DrawEngineType draw_engine_edit_surface_type;
extern DrawEngineType draw_engine_edit_text_type;
extern DrawEngineType draw_engine_paint_texture_type;
@@ -41,4 +42,4 @@ extern DrawEngineType draw_engine_particle_type;
extern DrawEngineType draw_engine_pose_type;
extern DrawEngineType draw_engine_sculpt_type;
#endif /* __DRAW_MODE_ENGINES_H__ */
#endif /* __DRAW_MODE_ENGINES_H__ */

View File

@@ -0,0 +1,308 @@
/*
* Copyright 2016, Blender Foundation.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Contributor(s): Blender Institute
*
*/
/** \file blender/draw/modes/particle_mode.c
* \ingroup draw
*/
#include "DRW_engine.h"
#include "DRW_render.h"
#include "BLI_ghash.h"
#include "DNA_view3d_types.h"
#include "BKE_editstrands.h"
#include "GPU_shader.h"
#include "GPU_texture.h"
#include "draw_common.h"
#include "draw_mode_engines.h"
extern GlobalsUboStorage ts;
extern char datatoc_edit_strands_vert_glsl[];
extern char datatoc_gpu_shader_point_varying_color_frag_glsl[];
extern char datatoc_gpu_shader_3D_smooth_color_frag_glsl[];
/* *********** LISTS *********** */
/* All lists are per viewport specific datas.
* They are all free when viewport changes engines
* or is free itself. Use EDIT_STRANDS_engine_init() to
* initialize most of them and EDIT_STRANDS_cache_init()
* for EDIT_STRANDS_PassList */
typedef struct EDIT_STRANDS_PassList {
/* Declare all passes here and init them in
* EDIT_STRANDS_cache_init().
* Only contains (DRWPass *) */
struct DRWPass *wires;
struct DRWPass *tips;
struct DRWPass *roots;
struct DRWPass *points;
} EDIT_STRANDS_PassList;
typedef struct EDIT_STRANDS_StorageList {
/* Contains any other memory block that the engine needs.
* Only directly MEM_(m/c)allocN'ed blocks because they are
* free with MEM_freeN() when viewport is freed.
* (not per object) */
struct CustomStruct *block;
struct EDIT_STRANDS_PrivateData *g_data;
} EDIT_STRANDS_StorageList;
typedef struct EDIT_STRANDS_Data {
/* Struct returned by DRW_viewport_engine_data_get.
* If you don't use one of these, just make it a (void *) */
// void *fbl;
void *engine_type; /* Required */
DRWViewportEmptyList *fbl;
DRWViewportEmptyList *txl;
EDIT_STRANDS_PassList *psl;
EDIT_STRANDS_StorageList *stl;
} EDIT_STRANDS_Data;
/* *********** STATIC *********** */
static struct {
/* Custom shaders :
* Add sources to source/blender/draw/modes/shaders
* init in EDIT_STRANDS_engine_init();
* free in EDIT_STRANDS_engine_free(); */
struct GPUShader *edit_point_shader;
struct GPUShader *edit_wire_shader;
} e_data = {NULL}; /* Engine data */
typedef struct EDIT_STRANDS_PrivateData {
/* resulting curve as 'wire' for fast editmode drawing */
DRWShadingGroup *wires_shgrp;
DRWShadingGroup *tips_shgrp;
DRWShadingGroup *roots_shgrp;
DRWShadingGroup *points_shgrp;
} EDIT_STRANDS_PrivateData; /* Transient data */
/* *********** FUNCTIONS *********** */
/* Init Textures, Framebuffers, Storage and Shaders.
* It is called for every frames.
* (Optional) */
static void EDIT_STRANDS_engine_init(void *vedata)
{
EDIT_STRANDS_StorageList *stl = ((EDIT_STRANDS_Data *)vedata)->stl;
UNUSED_VARS(stl);
/* Init Framebuffers like this: order is attachment order (for color texs) */
/*
* DRWFboTexture tex[2] = {{&txl->depth, DRW_TEX_DEPTH_24, 0},
* {&txl->color, DRW_TEX_RGBA_8, DRW_TEX_FILTER}};
*/
/* DRW_framebuffer_init takes care of checking if
* the framebuffer is valid and has the right size*/
/*
* float *viewport_size = DRW_viewport_size_get();
* DRW_framebuffer_init(&fbl->occlude_wire_fb,
* (int)viewport_size[0], (int)viewport_size[1],
* tex, 2);
*/
if (!e_data.edit_point_shader) {
e_data.edit_point_shader = DRW_shader_create(
datatoc_edit_strands_vert_glsl,
NULL,
datatoc_gpu_shader_point_varying_color_frag_glsl,
NULL);
}
if (!e_data.edit_wire_shader) {
e_data.edit_wire_shader = DRW_shader_create(
datatoc_edit_strands_vert_glsl,
NULL,
datatoc_gpu_shader_3D_smooth_color_frag_glsl,
NULL);
}
}
/* Cleanup when destroying the engine.
* This is not per viewport ! only when quitting blender.
* Mostly used for freeing shaders */
static void EDIT_STRANDS_engine_free(void)
{
DRW_SHADER_FREE_SAFE(e_data.edit_point_shader);
DRW_SHADER_FREE_SAFE(e_data.edit_wire_shader);
}
/* Here init all passes and shading groups
* Assume that all Passes are NULL */
static void EDIT_STRANDS_cache_init(void *vedata)
{
EDIT_STRANDS_PassList *psl = ((EDIT_STRANDS_Data *)vedata)->psl;
EDIT_STRANDS_StorageList *stl = ((EDIT_STRANDS_Data *)vedata)->stl;
if (!stl->g_data) {
/* Alloc transient pointers */
stl->g_data = MEM_mallocN(sizeof(*stl->g_data), __func__);
}
{
/* Strand wires */
DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS | DRW_STATE_BLEND;
psl->wires = DRW_pass_create("Strand Wire Verts Pass", state);
stl->g_data->wires_shgrp = DRW_shgroup_create(e_data.edit_wire_shader, psl->wires);
DRW_shgroup_uniform_vec4(stl->g_data->wires_shgrp, "color", ts.colorWireEdit, 1);
DRW_shgroup_uniform_vec4(stl->g_data->wires_shgrp, "colorSelect", ts.colorEdgeSelect, 1);
}
{
/* Tip vertices */
DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_LESS | DRW_STATE_BLEND;
psl->tips = DRW_pass_create("Strand Tip Verts Pass", state);
stl->g_data->tips_shgrp = DRW_shgroup_create(e_data.edit_point_shader, psl->tips);
DRW_shgroup_uniform_vec4(stl->g_data->tips_shgrp, "color", ts.colorVertex, 1);
DRW_shgroup_uniform_vec4(stl->g_data->tips_shgrp, "colorSelect", ts.colorVertexSelect, 1);
DRW_shgroup_uniform_float(stl->g_data->tips_shgrp, "sizeVertex", &ts.sizeVertex, 1);
}
{
/* Root vertices */
DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_LESS | DRW_STATE_BLEND;
psl->roots = DRW_pass_create("Strand Root Verts Pass", state);
stl->g_data->roots_shgrp = DRW_shgroup_create(e_data.edit_point_shader, psl->roots);
DRW_shgroup_uniform_vec4(stl->g_data->roots_shgrp, "color", ts.colorVertex, 1);
DRW_shgroup_uniform_vec4(stl->g_data->roots_shgrp, "colorSelect", ts.colorVertexSelect, 1);
DRW_shgroup_uniform_float(stl->g_data->roots_shgrp, "sizeVertex", &ts.sizeVertex, 1);
}
{
/* Interior vertices */
DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_LESS | DRW_STATE_BLEND;
psl->points = DRW_pass_create("Strand Interior Verts Pass", state);
stl->g_data->points_shgrp = DRW_shgroup_create(e_data.edit_point_shader, psl->points);
DRW_shgroup_uniform_vec4(stl->g_data->points_shgrp, "color", ts.colorVertex, 1);
DRW_shgroup_uniform_vec4(stl->g_data->points_shgrp, "colorSelect", ts.colorVertexSelect, 1);
DRW_shgroup_uniform_float(stl->g_data->points_shgrp, "sizeVertex", &ts.sizeVertex, 1);
}
}
static void edit_strands_add_ob_to_pass(
Scene *scene, Object *ob, BMEditStrands *edit,
DRWShadingGroup *tips_shgrp,
DRWShadingGroup *roots_shgrp,
DRWShadingGroup *points_shgrp,
DRWShadingGroup *wires_shgrp)
{
HairEditSettings *tsettings = &scene->toolsettings->hair_edit;
{
struct Gwn_Batch *geom = DRW_cache_editstrands_get_wires(edit);
DRW_shgroup_call_add(wires_shgrp, geom, ob->obmat);
}
switch (tsettings->select_mode) {
case HAIR_SELECT_TIP: {
struct Gwn_Batch *geom = DRW_cache_editstrands_get_tips(edit);
DRW_shgroup_call_add(tips_shgrp, geom, ob->obmat);
break;
}
case HAIR_SELECT_STRAND: {
#if 0
struct Gwn_Batch *geom = DRW_cache_editstrands_get_roots(edit);
DRW_shgroup_call_add(roots_shgrp, geom, ob->obmat);
#else
UNUSED_VARS(roots_shgrp);
#endif
break;
}
case HAIR_SELECT_VERTEX: {
struct Gwn_Batch *geom = DRW_cache_editstrands_get_points(edit);
DRW_shgroup_call_add(points_shgrp, geom, ob->obmat);
break;
}
}
}
/* Add geometry to shadingGroups. Execute for each objects */
static void EDIT_STRANDS_cache_populate(void *vedata, Object *ob)
{
EDIT_STRANDS_StorageList *stl = ((EDIT_STRANDS_Data *)vedata)->stl;
BMEditStrands *edit = BKE_editstrands_from_object(ob);
const DRWContextState *draw_ctx = DRW_context_state_get();
Scene *scene = draw_ctx->scene;
// Don't draw strands while editing the object itself
if (ob == scene->obedit)
return;
if (edit) {
edit_strands_add_ob_to_pass(scene, ob, edit,
stl->g_data->tips_shgrp, stl->g_data->roots_shgrp,
stl->g_data->points_shgrp, stl->g_data->wires_shgrp);
}
}
/* Optional: Post-cache_populate callback */
static void EDIT_STRANDS_cache_finish(void *vedata)
{
EDIT_STRANDS_PassList *psl = ((EDIT_STRANDS_Data *)vedata)->psl;
EDIT_STRANDS_StorageList *stl = ((EDIT_STRANDS_Data *)vedata)->stl;
/* Do something here! dependant on the objects gathered */
UNUSED_VARS(psl, stl);
}
/* Draw time ! Control rendering pipeline from here */
static void EDIT_STRANDS_draw_scene(void *vedata)
{
EDIT_STRANDS_PassList *psl = ((EDIT_STRANDS_Data *)vedata)->psl;
DRW_draw_pass(psl->wires);
DRW_draw_pass(psl->points);
DRW_draw_pass(psl->roots);
DRW_draw_pass(psl->tips);
/* If you changed framebuffer, double check you rebind
* the default one with its textures attached before finishing */
}
static const DrawEngineDataSize STRANDS_data_size = DRW_VIEWPORT_DATA_SIZE(EDIT_STRANDS_Data);
DrawEngineType draw_engine_edit_strands_type = {
NULL, NULL,
N_("EditStrandsMode"),
&STRANDS_data_size,
EDIT_STRANDS_engine_init,
EDIT_STRANDS_engine_free,
&EDIT_STRANDS_cache_init,
&EDIT_STRANDS_cache_populate,
&EDIT_STRANDS_cache_finish,
NULL, /* draw_background but not needed by mode engines */
&EDIT_STRANDS_draw_scene
};

View File

@@ -1674,6 +1674,8 @@ static void OBJECT_cache_populate(void *vedata, Object *ob)
SceneLayer *sl = draw_ctx->scene_layer;
View3D *v3d = draw_ctx->v3d;
int theme_id = TH_UNDEFINED;
const bool is_edited = (ob == scene->obedit) ||
((ob == draw_ctx->obact) && (ob->mode & OB_MODE_ALL_BRUSH));
//CollectionEngineSettings *ces_mode_ob = BKE_layer_collection_engine_evaluated_get(ob, COLLECTION_MODE_OBJECT, "");
@@ -1681,8 +1683,7 @@ static void OBJECT_cache_populate(void *vedata, Object *ob)
bool do_outlines = ((ob->base_flag & BASE_SELECTED) != 0);
if (do_outlines) {
Object *obedit = scene->obedit;
if (ob != obedit && !((ob == draw_ctx->obact) && (ob->mode & OB_MODE_ALL_PAINT))) {
if (!is_edited) {
struct Gwn_Batch *geom = DRW_cache_object_surface_get(ob);
if (geom) {
theme_id = DRW_object_wire_theme_get(ob, sl, NULL);
@@ -1699,8 +1700,7 @@ static void OBJECT_cache_populate(void *vedata, Object *ob)
{
Mesh *me = ob->data;
if (me->totpoly == 0) {
Object *obedit = scene->obedit;
if (ob != obedit) {
if (!is_edited) {
struct Gwn_Batch *geom = DRW_cache_mesh_edges_get(ob);
if (geom) {
if (theme_id == TH_UNDEFINED) {
@@ -1720,8 +1720,7 @@ static void OBJECT_cache_populate(void *vedata, Object *ob)
break;
case OB_LATTICE:
{
Object *obedit = scene->obedit;
if (ob != obedit) {
if (!is_edited) {
struct Gwn_Batch *geom = DRW_cache_lattice_wire_get(ob, false);
if (theme_id == TH_UNDEFINED) {
theme_id = DRW_object_wire_theme_get(ob, sl, NULL);
@@ -1735,8 +1734,7 @@ static void OBJECT_cache_populate(void *vedata, Object *ob)
case OB_CURVE:
{
Object *obedit = scene->obedit;
if (ob != obedit) {
if (!is_edited) {
struct Gwn_Batch *geom = DRW_cache_curve_edge_wire_get(ob);
if (theme_id == TH_UNDEFINED) {
theme_id = DRW_object_wire_theme_get(ob, sl, NULL);

View File

@@ -0,0 +1,28 @@
/* Draw Curve Vertices */
uniform mat4 ModelViewProjectionMatrix;
uniform vec2 viewportSize;
uniform vec4 color;
uniform vec4 colorSelect;
uniform float sizeVertex;
in vec3 pos;
in int flag;
out vec4 finalColor;
#define VERTEX_SELECTED (1 << 0)
void main()
{
gl_Position = ModelViewProjectionMatrix * vec4(pos, 1.0);
gl_PointSize = sizeVertex;
if ((flag & VERTEX_SELECTED) != 0) {
finalColor = colorSelect;
}
else {
finalColor = color;
}
}

View File

@@ -27,6 +27,7 @@ if(WITH_BLENDER)
add_subdirectory(armature)
add_subdirectory(curve)
add_subdirectory(gpencil)
add_subdirectory(hair)
add_subdirectory(interface)
add_subdirectory(io)
add_subdirectory(manipulator_library)

View File

@@ -833,7 +833,7 @@ static void *get_armature_edit(bContext *C)
void undo_push_armature(bContext *C, const char *name)
{
// XXX solve getdata()
undo_editmode_push(C, name, get_armature_edit, free_undoBones, undoBones_to_editBones, editBones_to_undoBones, NULL);
undo_editmode_push(C, name, CTX_data_edit_object, get_armature_edit, free_undoBones, undoBones_to_editBones, editBones_to_undoBones, NULL);
}
/* *************************************************************** */

View File

@@ -6227,7 +6227,7 @@ static void *get_data(bContext *C)
/* and this is all the undo system needs to know */
void undo_push_curve(bContext *C, const char *name)
{
undo_editmode_push(C, name, get_data, free_undoCurve, undoCurve_to_editCurve, editCurve_to_undoCurve, NULL);
undo_editmode_push(C, name, CTX_data_edit_object, get_data, free_undoCurve, undoCurve_to_editCurve, editCurve_to_undoCurve, NULL);
}
void ED_curve_beztcpy(EditNurb *editnurb, BezTriple *dst, BezTriple *src, int count)

View File

@@ -307,5 +307,5 @@ static void *get_undoFont(bContext *C)
/* and this is all the undo system needs to know */
void undo_push_font(bContext *C, const char *name)
{
undo_editmode_push(C, name, get_undoFont, free_undoFont, undoFont_to_editFont, editFont_to_undoFont, NULL);
undo_editmode_push(C, name, CTX_data_edit_object, get_undoFont, free_undoFont, undoFont_to_editFont, editFont_to_undoFont, NULL);
}

View File

@@ -78,6 +78,13 @@ if(WITH_BLENDER)
data_to_c_simple(../../../../release/datafiles/brushicons/fill.png SRC)
data_to_c_simple(../../../../release/datafiles/brushicons/flatten.png SRC)
data_to_c_simple(../../../../release/datafiles/brushicons/grab.png SRC)
data_to_c_simple(../../../../release/datafiles/brushicons/hairadd.png SRC)
data_to_c_simple(../../../../release/datafiles/brushicons/haircomb.png SRC)
data_to_c_simple(../../../../release/datafiles/brushicons/haircut.png SRC)
data_to_c_simple(../../../../release/datafiles/brushicons/hairlength.png SRC)
data_to_c_simple(../../../../release/datafiles/brushicons/hairpuff.png SRC)
data_to_c_simple(../../../../release/datafiles/brushicons/hairsmooth.png SRC)
data_to_c_simple(../../../../release/datafiles/brushicons/hairweight.png SRC)
data_to_c_simple(../../../../release/datafiles/brushicons/inflate.png SRC)
data_to_c_simple(../../../../release/datafiles/brushicons/layer.png SRC)
data_to_c_simple(../../../../release/datafiles/brushicons/lighten.png SRC)

View File

@@ -0,0 +1,57 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# Contributor(s): Jacques Beaurain.
#
# ***** END GPL LICENSE BLOCK *****
set(INC
../include
../sculpt_paint
../../blenfont
../../blenkernel
../../blenlib
../../bmesh
../../depsgraph
../../gpu
../../makesdna
../../makesrna
../../windowmanager
../../../../intern/guardedalloc
../../../../intern/glew-mx
)
set(INC_SYS
${GLEW_INCLUDE_PATH}
)
set(SRC
hair_cursor.c
hair_edit.c
hair_mirror.c
hair_object_mesh.c
hair_object_particles.c
hair_ops.c
hair_select.c
hair_stroke.c
hair_undo.c
hair_intern.h
)
add_definitions(${GL_DEFINITIONS})
blender_add_lib(bf_editor_hair "${SRC}" "${INC}" "${INC_SYS}")

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env python
#
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# The Original Code is Copyright (C) 2006, Blender Foundation
# All rights reserved.
#
# The Original Code is: all of this file.
#
# Contributor(s): Nathan Letwory.
#
# ***** END GPL LICENSE BLOCK *****
Import ('env')
sources = env.Glob('*.c')
incs = [
'#/intern/guardedalloc',
env['BF_GLEW_INC'],
'#/intern/glew-mx',
'../include',
'../sculpt_paint',
'../../blenfont',
'../../blenkernel',
'../../blenlib',
'../../bmesh',
'../../gpu',
'../../makesdna',
'../../makesrna',
'../../windowmanager',
]
incs = ' '.join(incs)
defs = ['BF_GL_DEFINITIONS']
if env['OURPLATFORM'] in ('win32-vc', 'win32-mingw', 'linuxcross', 'win64-vc', 'win64-mingw'):
incs += ' ' + env['BF_PTHREADS_INC']
env.BlenderLib ( 'bf_editors_hair', sources, Split(incs), defs, libtype=['core'], priority=[45] )

View File

@@ -0,0 +1,100 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) Blender Foundation
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Lukas Toenne
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/editors/hair/hair_cursor.c
* \ingroup edhair
*/
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
#include "BLI_math.h"
#include "DNA_brush_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_view3d_types.h"
#include "BKE_brush.h"
#include "BKE_context.h"
#include "GPU_immediate.h"
#include "GPU_immediate_util.h"
#include "WM_api.h"
#include "WM_types.h"
#include "BIF_gl.h"
#include "BIF_glutil.h"
#include "ED_view3d.h"
#include "hair_intern.h"
static void hair_draw_cursor(bContext *C, int x, int y, void *UNUSED(customdata))
{
Scene *scene = CTX_data_scene(C);
UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
HairEditSettings *settings = &scene->toolsettings->hair_edit;
Brush *brush = settings->brush;
if (!brush)
return;
float final_radius = BKE_brush_size_get(scene, brush);
float col[4];
/* set various defaults */
copy_v3_v3(col, brush->add_col);
col[3] = 0.5f;
Gwn_VertFormat *format = immVertexFormat();
uint pos = GWN_vertformat_attr_add(format, "pos", GWN_COMP_F32, 2, GWN_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
immUniformColor4fv(col);
/* draw an inner brush */
if (ups->stroke_active && BKE_brush_use_size_pressure(scene, brush)) {
/* inner at full alpha */
imm_draw_circle_wire(pos, x, y, final_radius * ups->size_pressure_value, 40);
/* outer at half alpha */
immUniformColor3fvAlpha(col, col[3]*0.5f);
}
imm_draw_circle_wire(pos, x, y, final_radius, 40);
immUnbindProgram();
}
void hair_edit_cursor_start(bContext *C, int (*poll)(bContext *C))
{
Scene *scene = CTX_data_scene(C);
HairEditSettings *settings = &scene->toolsettings->hair_edit;
if (!settings->paint_cursor)
settings->paint_cursor = WM_paint_cursor_activate(CTX_wm_manager(C), poll, hair_draw_cursor, NULL);
}

View File

@@ -0,0 +1,465 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) Blender Foundation
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Lukas Toenne
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/editors/hair/hair_edit.c
* \ingroup edhair
*/
#include <stdlib.h>
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
#include "BLI_math.h"
#include "DNA_brush_types.h"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_view3d_types.h"
#include "BKE_brush.h"
#include "BKE_cdderivedmesh.h"
#include "BKE_context.h"
#include "BKE_DerivedMesh.h"
#include "BKE_editstrands.h"
#include "BKE_paint.h"
#include "DEG_depsgraph.h"
#include "bmesh.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "WM_api.h"
#include "WM_types.h"
#include "ED_object.h"
#include "ED_view3d.h"
#include "hair_intern.h"
#include "paint_intern.h"
#define USE_PARTICLES 1
int hair_edit_poll(bContext *C)
{
Object *obact;
obact = CTX_data_active_object(C);
if ((obact && obact->mode & OB_MODE_HAIR_EDIT) && CTX_wm_region_view3d(C)) {
return true;
}
return false;
}
bool hair_use_mirror_x(Object *ob)
{
if (ob->type == OB_MESH)
return ((Mesh *)ob->data)->editflag & ME_EDIT_MIRROR_X;
else
return false;
}
bool hair_use_mirror_topology(Object *ob)
{
if (ob->type == OB_MESH)
return ((Mesh *)ob->data)->editflag & ME_EDIT_MIRROR_TOPO;
else
return false;
}
/* ==== BMesh utilities ==== */
void hair_bm_min_max(BMEditStrands *edit, float min[3], float max[3])
{
BMesh *bm = edit->base.bm;
BMVert *v;
BMIter iter;
if (bm->totvert > 0) {
INIT_MINMAX(min, max);
BM_ITER_MESH(v, &iter, bm, BM_VERTS_OF_MESH) {
minmax_v3v3_v3(min, max, v->co);
}
}
else {
zero_v3(min);
zero_v3(max);
}
}
/* ==== edit mode toggle ==== */
int hair_edit_toggle_poll(bContext *C)
{
Object *ob = CTX_data_active_object(C);
if (ob == NULL)
return false;
if (!ob->data || ((ID *)ob->data)->lib)
return false;
if (CTX_data_edit_object(C))
return false;
#if USE_PARTICLES
return ED_hair_object_has_hair_particle_data(ob);
#else
return ob->type == OB_MESH;
#endif
}
static void toggle_hair_cursor(bContext *C, bool enable)
{
wmWindowManager *wm = CTX_wm_manager(C);
Scene *scene = CTX_data_scene(C);
HairEditSettings *settings = &scene->toolsettings->hair_edit;
if (enable) {
hair_edit_cursor_start(C, hair_edit_toggle_poll);
}
else {
if (settings->paint_cursor) {
WM_paint_cursor_end(wm, settings->paint_cursor);
settings->paint_cursor = NULL;
}
}
}
static int hair_edit_toggle_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
const int mode_flag = OB_MODE_HAIR_EDIT;
const bool is_mode_set = (ob->mode & mode_flag) != 0;
EvaluationContext eval_ctx;
CTX_data_eval_ctx(C, &eval_ctx);
if (!is_mode_set) {
if (!ED_object_mode_compat_set(C, ob, mode_flag, op->reports)) {
return OPERATOR_CANCELLED;
}
}
if (!is_mode_set) {
#if USE_PARTICLES
ED_hair_object_init_particle_edit(&eval_ctx, scene, ob);
#else
ED_hair_object_init_mesh_edit(&eval_ctx, scene, ob);
#endif
ob->mode |= mode_flag;
toggle_hair_cursor(C, true);
WM_event_add_notifier(C, NC_SCENE|ND_MODE|NS_MODE_HAIR, NULL);
}
else {
#if USE_PARTICLES
ED_hair_object_apply_particle_edit(ob);
#else
ED_hair_object_apply_mesh_edit(ob);
#endif
ob->mode &= ~mode_flag;
toggle_hair_cursor(C, false);
WM_event_add_notifier(C, NC_SCENE|ND_MODE|NS_MODE_HAIR, NULL);
}
DEG_id_tag_update(&ob->id, OB_RECALC_DATA);
return OPERATOR_FINISHED;
}
void HAIR_OT_hair_edit_toggle(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Hair Edit Toggle";
ot->idname = "HAIR_OT_hair_edit_toggle";
ot->description = "Toggle hair edit mode";
/* api callbacks */
ot->exec = hair_edit_toggle_exec;
ot->poll = hair_edit_toggle_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ==== brush stroke ==== */
void hair_init_viewcontext(bContext *C, ViewContext *vc)
{
View3D *v3d;
bool has_zbuf;
view3d_set_viewcontext(C, vc);
v3d = vc->v3d;
has_zbuf = (v3d->drawtype > OB_WIRE) && (v3d->flag & V3D_ZBUF_SELECT);
if (has_zbuf) {
if (v3d->flag & V3D_INVALID_BACKBUF) {
/* needed or else the draw matrix can be incorrect */
view3d_operator_needs_opengl(C);
ED_view3d_backbuf_validate(C, vc);
/* we may need to force an update here by setting the rv3d as dirty
* for now it seems ok, but take care!:
* rv3d->depths->dirty = 1; */
ED_view3d_depth_update(vc->ar);
}
}
}
typedef struct HairStroke {
Scene *scene;
Object *ob;
BMEditStrands *edit;
bool first;
float lastmouse[2];
float zfac;
float smoothdir[2];
} HairStroke;
static int hair_stroke_init(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
BMEditStrands *edit = BKE_editstrands_from_object(ob);
ARegion *ar = CTX_wm_region(C);
HairStroke *stroke;
float min[3], max[3], center[3];
/* set the 'distance factor' for grabbing (used in comb etc) */
hair_bm_min_max(edit, min, max);
mid_v3_v3v3(center, min, max);
stroke = MEM_callocN(sizeof(HairStroke), "HairStroke");
stroke->first = true;
op->customdata = stroke;
stroke->scene = scene;
stroke->ob = ob;
stroke->edit = edit;
stroke->zfac = ED_view3d_calc_zfac(ar->regiondata, center, NULL);
return 1;
}
static bool hair_stroke_apply(bContext *C, wmOperator *op, PointerRNA *itemptr)
{
HairStroke *stroke = op->customdata;
Scene *scene = stroke->scene;
Object *ob = stroke->ob;
BMEditStrands *edit = stroke->edit;
HairEditSettings *settings = &scene->toolsettings->hair_edit;
ARegion *ar = CTX_wm_region(C);
const float smoothfac = 0.9f; /* XXX should this be configurable? */
float mouse[2], mdelta[2], zvec[3], delta_max;
int totsteps, step;
HairToolData tool_data;
bool updated = false;
RNA_float_get_array(itemptr, "mouse", mouse);
if (stroke->first) {
copy_v2_v2(stroke->lastmouse, mouse);
zero_v2(stroke->smoothdir);
stroke->first = false;
}
if (!settings->brush)
return false;
sub_v2_v2v2(mdelta, mouse, stroke->lastmouse);
delta_max = max_ff(fabsf(mdelta[0]), fabsf(mdelta[1]));
totsteps = delta_max / (0.2f * BKE_brush_size_get(scene, settings->brush)) + 1;
mul_v2_fl(mdelta, 1.0f / (float)totsteps);
/* low-pass filter to smooth out jittery pixel increments in the direction */
interp_v2_v2v2(stroke->smoothdir, mdelta, stroke->smoothdir, smoothfac);
hair_init_viewcontext(C, &tool_data.vc);
tool_data.scene = scene;
tool_data.ob = ob;
tool_data.edit = edit;
tool_data.settings = settings;
invert_m4_m4(tool_data.imat, ob->obmat);
copy_v2_v2(tool_data.mval, mouse);
tool_data.mdepth = stroke->zfac;
zvec[0] = 0.0f; zvec[1] = 0.0f; zvec[2] = stroke->zfac;
ED_view3d_win_to_3d(tool_data.vc.v3d, ar, zvec, mouse, tool_data.loc);
ED_view3d_win_to_delta(ar, stroke->smoothdir, tool_data.delta, stroke->zfac);
/* tools work in object space */
mul_m4_v3(tool_data.imat, tool_data.loc);
mul_mat3_m4_v3(tool_data.imat, tool_data.delta);
for (step = 0; step < totsteps; ++step) {
bool step_updated = hair_brush_step(&tool_data);
if (step_updated)
BKE_editstrands_solve_constraints(ob, edit, NULL);
updated |= step_updated;
}
copy_v2_v2(stroke->lastmouse, mouse);
return updated;
}
static void hair_stroke_exit(wmOperator *op)
{
HairStroke *stroke = op->customdata;
MEM_freeN(stroke);
}
static int hair_stroke_exec(bContext *C, wmOperator *op)
{
HairStroke *stroke = op->customdata;
Object *ob = stroke->ob;
bool updated = false;
if (!hair_stroke_init(C, op))
return OPERATOR_CANCELLED;
RNA_BEGIN (op->ptr, itemptr, "stroke")
{
updated |= hair_stroke_apply(C, op, &itemptr);
}
RNA_END;
if (updated) {
DEG_id_tag_update(&ob->id, OB_RECALC_DATA);
WM_event_add_notifier(C, NC_OBJECT|ND_MODIFIER, ob);
}
hair_stroke_exit(op);
return OPERATOR_FINISHED;
}
static void hair_stroke_apply_event(bContext *C, wmOperator *op, const wmEvent *event)
{
HairStroke *stroke = op->customdata;
Object *ob = stroke->ob;
PointerRNA itemptr;
float mouse[2];
bool updated = false;
mouse[0] = event->mval[0];
mouse[1] = event->mval[1];
/* fill in stroke */
RNA_collection_add(op->ptr, "stroke", &itemptr);
RNA_float_set_array(&itemptr, "mouse", mouse);
/* apply */
updated |= hair_stroke_apply(C, op, &itemptr);
if (updated) {
DEG_id_tag_update(&ob->id, OB_RECALC_DATA);
WM_event_add_notifier(C, NC_OBJECT|ND_MODIFIER, ob);
}
else {
/* even if nothing was changed, still trigger redraw
* for brush drawing during the modal operator
*/
WM_event_add_notifier(C, NC_OBJECT|ND_DRAW, ob);
}
}
static int hair_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
if (!hair_stroke_init(C, op))
return OPERATOR_CANCELLED;
hair_stroke_apply_event(C, op, event);
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
static int hair_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
switch (event->type) {
case LEFTMOUSE:
case MIDDLEMOUSE:
case RIGHTMOUSE: // XXX hardcoded
hair_stroke_exit(op);
return OPERATOR_FINISHED;
case MOUSEMOVE:
hair_stroke_apply_event(C, op, event);
break;
}
return OPERATOR_RUNNING_MODAL;
}
static void hair_stroke_cancel(bContext *UNUSED(C), wmOperator *op)
{
hair_stroke_exit(op);
}
void HAIR_OT_stroke(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Hair Stroke";
ot->idname = "HAIR_OT_stroke";
ot->description = "Use a stroke tool on hair strands";
/* api callbacks */
ot->exec = hair_stroke_exec;
ot->invoke = hair_stroke_invoke;
ot->modal = hair_stroke_modal;
ot->cancel = hair_stroke_cancel;
ot->poll = hair_edit_poll;
/* flags */
ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO|OPTYPE_BLOCKING;
/* properties */
RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
}

View File

@@ -0,0 +1,115 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) Blender Foundation
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Lukas Toenne
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/editors/hair/hair_intern.h
* \ingroup edhair
*/
#ifndef __HAIR_INTERN_H__
#define __HAIR_INTERN_H__
#include "BIF_glutil.h"
#include "ED_view3d.h"
struct ARegion;
struct bContext;
struct wmOperatorType;
struct rcti;
struct EvaluationContext;
struct Object;
/* hair_edit.c */
bool hair_use_mirror_x(struct Object *ob);
bool hair_use_mirror_topology(struct Object *ob);
int hair_edit_toggle_poll(struct bContext *C);
int hair_edit_poll(struct bContext *C);
void HAIR_OT_hair_edit_toggle(struct wmOperatorType *ot);
/* hair_select.c */
void HAIR_OT_select_all(struct wmOperatorType *ot);
void HAIR_OT_select_linked(struct wmOperatorType *ot);
/* hair_stroke.c */
void HAIR_OT_stroke(struct wmOperatorType *ot);
/* hair_object_mesh.c */
bool ED_hair_object_init_mesh_edit(struct EvaluationContext *eval_ctx, struct Scene *scene, struct Object *ob);
bool ED_hair_object_apply_mesh_edit(struct Object *ob);
/* hair_object_particles.c */
bool ED_hair_object_has_hair_particle_data(struct Object *ob);
bool ED_hair_object_init_particle_edit(struct EvaluationContext *eval_ctx, struct Scene *scene, struct Object *ob);
bool ED_hair_object_apply_particle_edit(struct Object *ob);
/* ==== Hair Brush ==== */
void hair_init_viewcontext(struct bContext *C, struct ViewContext *vc);
bool hair_test_depth(struct ViewContext *vc, const float co[3], const int screen_co[2]);
bool hair_test_vertex_inside_circle(struct ViewContext *vc, const float mval[2], float radsq,
struct BMVert *v, float *r_dist);
bool hair_test_edge_inside_circle(struct ViewContext *vc, const float mval[2], float radsq,
struct BMVert *v1, struct BMVert *v2, float *r_dist, float *r_lambda);
bool hair_test_vertex_inside_rect(struct ViewContext *vc, struct rcti *rect, struct BMVert *v);
bool hair_test_vertex_inside_lasso(struct ViewContext *vc, const int mcoords[][2], short moves, struct BMVert *v);
typedef struct HairToolData {
/* context */
struct Scene *scene;
struct Object *ob;
struct BMEditStrands *edit;
struct HairEditSettings *settings;
ViewContext vc;
/* view space */
float mval[2]; /* mouse coordinates */
float mdepth; /* mouse z depth */
/* object space */
float imat[4][4]; /* obmat inverse */
float loc[3]; /* start location */
float delta[3]; /* stroke step */
} HairToolData;
bool hair_brush_step(struct HairToolData *data);
/* ==== Cursor ==== */
void hair_edit_cursor_start(struct bContext *C, int (*poll)(struct bContext *C));
/* ==== BMesh utilities ==== */
struct BMEditStrands;
void hair_bm_min_max(struct BMEditStrands *edit, float min[3], float max[3]);
#endif

View File

@@ -0,0 +1,217 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) Blender Foundation
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Lukas Toenne
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/editors/hair/hair_mirror.c
* \ingroup edhair
*/
#include <stdlib.h>
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
#include "BLI_math.h"
#include "BKE_editmesh_bvh.h"
#include "BKE_editstrands.h"
#include "ED_physics.h"
#include "ED_util.h"
#include "bmesh.h"
#include "hair_intern.h"
/* TODO use_topology is not yet implemented for strands.
* Native strand topology is not very useful for this.
* Instead, the topology of the scalp mesh should be used for finding mirrored strand roots,
* then the arc- or parametric length of a vertex from the root to find mirrored verts.
*/
#define BM_SEARCH_MAXDIST_MIRR 0.00002f
#define BM_CD_LAYER_ID "__mirror_index"
/**
* \param edit Edit strands.
* \param use_self Allow a vertex to point to its self (middle verts).
* \param use_select Restrict to selected verts.
* \param use_topology Use topology mirror.
* \param maxdist Distance for close point test.
* \param r_index Optional array to write into, as an alternative to a customdata layer (length of total verts).
*/
void ED_strands_mirror_cache_begin_ex(BMEditStrands *edit, const int axis, const bool use_self, const bool use_select,
/* extra args */
const bool UNUSED(use_topology), float maxdist, int *r_index)
{
BMesh *bm = edit->base.bm;
BMIter iter;
BMVert *v;
int cd_vmirr_offset;
int i;
/* one or the other is used depending if topo is enabled */
struct BMBVHTree *tree = NULL;
BM_mesh_elem_table_ensure(bm, BM_VERT);
if (r_index == NULL) {
const char *layer_id = BM_CD_LAYER_ID;
int mirror_cdlayer = CustomData_get_named_layer_index(&bm->vdata, CD_PROP_INT, layer_id);
if (mirror_cdlayer == -1) {
BM_data_layer_add_named(bm, &bm->vdata, CD_PROP_INT, layer_id);
mirror_cdlayer = CustomData_get_named_layer_index(&bm->vdata, CD_PROP_INT, layer_id);
}
cd_vmirr_offset = CustomData_get_n_offset(&bm->vdata, CD_PROP_INT,
mirror_cdlayer - CustomData_get_layer_index(&bm->vdata, CD_PROP_INT));
bm->vdata.layers[mirror_cdlayer].flag |= CD_FLAG_TEMPORARY;
edit->base.mirror_cdlayer = mirror_cdlayer;
}
BM_mesh_elem_index_ensure(bm, BM_VERT);
tree = BKE_bmbvh_new(edit->base.bm, NULL, 0, 0, NULL, false);
#define VERT_INTPTR(_v, _i) r_index ? &r_index[_i] : BM_ELEM_CD_GET_VOID_P(_v, cd_vmirr_offset);
BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) {
BLI_assert(BM_elem_index_get(v) == i);
/* temporary for testing, check for selection */
if (use_select && !BM_elem_flag_test(v, BM_ELEM_SELECT)) {
/* do nothing */
}
else {
BMVert *v_mirr;
float co[3];
int *idx = VERT_INTPTR(v, i);
copy_v3_v3(co, v->co);
co[axis] *= -1.0f;
v_mirr = BKE_bmbvh_find_vert_closest(tree, co, maxdist);
if (v_mirr && (use_self || (v_mirr != v))) {
const int i_mirr = BM_elem_index_get(v_mirr);
*idx = i_mirr;
idx = VERT_INTPTR(v_mirr, i_mirr);
*idx = i;
}
else {
*idx = -1;
}
}
}
#undef VERT_INTPTR
BKE_bmbvh_free(tree);
}
void ED_strands_mirror_cache_begin(BMEditStrands *edit, const int axis,
const bool use_self, const bool use_select,
const bool use_topology)
{
ED_strands_mirror_cache_begin_ex(edit, axis,
use_self, use_select,
/* extra args */
use_topology, BM_SEARCH_MAXDIST_MIRR, NULL);
}
BMVert *ED_strands_mirror_get(BMEditStrands *edit, BMVert *v)
{
BMesh *bm = edit->base.bm;
int mirror_cdlayer = edit->base.mirror_cdlayer;
const int *mirr = CustomData_bmesh_get_layer_n(&bm->vdata, v->head.data, mirror_cdlayer);
BLI_assert(mirror_cdlayer != -1); /* invalid use */
if (mirr && *mirr >= 0 && *mirr < bm->totvert) {
if (!bm->vtable) {
printf("err: should only be called between "
"ED_strands_mirror_cache_begin and ED_strands_mirror_cache_end\n");
return NULL;
}
return bm->vtable[*mirr];
}
return NULL;
}
BMEdge *ED_strands_mirror_get_edge(BMEditStrands *edit, BMEdge *e)
{
BMVert *v1_mirr = ED_strands_mirror_get(edit, e->v1);
if (v1_mirr) {
BMVert *v2_mirr = ED_strands_mirror_get(edit, e->v2);
if (v2_mirr) {
return BM_edge_exists(v1_mirr, v2_mirr);
}
}
return NULL;
}
void ED_strands_mirror_cache_clear(BMEditStrands *edit, BMVert *v)
{
BMesh *bm = edit->base.bm;
int mirror_cdlayer = edit->base.mirror_cdlayer;
int *mirr = CustomData_bmesh_get_layer_n(&bm->vdata, v->head.data, mirror_cdlayer);
BLI_assert(mirror_cdlayer != -1); /* invalid use */
if (mirr) {
*mirr = -1;
}
}
void ED_strands_mirror_cache_end(BMEditStrands *edit)
{
edit->base.mirror_cdlayer = -1;
}
void ED_strands_mirror_apply(BMEditStrands *edit, const int sel_from, const int sel_to)
{
BMesh *bm = edit->base.bm;
BMIter iter;
BMVert *v;
BLI_assert((bm->vtable != NULL) && ((bm->elem_table_dirty & BM_VERT) == 0));
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_flag_test(v, BM_ELEM_SELECT) == sel_from) {
BMVert *mirr = ED_strands_mirror_get(edit, v);
if (mirr) {
if (BM_elem_flag_test(mirr, BM_ELEM_SELECT) == sel_to) {
copy_v3_v3(mirr->co, v->co);
mirr->co[0] *= -1.0f;
}
}
}
}
}

View File

@@ -0,0 +1,86 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) Blender Foundation
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Lukas Toenne
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/editors/hair/hair_object_mesh.c
* \ingroup edhair
*/
#include <stdlib.h>
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BKE_cdderivedmesh.h"
#include "BKE_DerivedMesh.h"
#include "BKE_editstrands.h"
#include "bmesh.h"
#include "hair_intern.h"
bool ED_hair_object_init_mesh_edit(struct EvaluationContext *UNUSED(eval_ctx), Scene *UNUSED(scene), Object *ob)
{
if (ob->type == OB_MESH) {
Mesh *me = ob->data;
if (!me->edit_strands) {
BMesh *bm = BKE_editstrands_mesh_to_bmesh(ob, me);
DerivedMesh *root_dm = CDDM_new(0, 0, 0, 0, 0);
me->edit_strands = BKE_editstrands_create(bm, root_dm);
root_dm->release(root_dm);
}
return true;
}
return false;
}
bool ED_hair_object_apply_mesh_edit(Object *ob)
{
if (ob->type == OB_MESH) {
Mesh *me = ob->data;
if (me->edit_strands) {
BKE_editstrands_mesh_from_bmesh(ob);
BKE_editstrands_free(me->edit_strands);
MEM_freeN(me->edit_strands);
me->edit_strands = NULL;
}
return true;
}
return false;
}

View File

@@ -0,0 +1,101 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) Blender Foundation
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Lukas Toenne
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/editors/hair/hair_object_particles.c
* \ingroup edhair
*/
#include <stdlib.h>
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "DNA_particle_types.h"
#include "DNA_scene_types.h"
#include "BKE_cdderivedmesh.h"
#include "BKE_DerivedMesh.h"
#include "BKE_editstrands.h"
#include "BKE_particle.h"
#include "bmesh.h"
#include "hair_intern.h"
bool ED_hair_object_has_hair_particle_data(Object *ob)
{
ParticleSystem *psys = psys_get_current(ob);
if (psys && psys->part->type == PART_HAIR)
return true;
return false;
}
bool ED_hair_object_init_particle_edit(struct EvaluationContext *eval_ctx, Scene *scene, Object *ob)
{
ParticleSystem *psys = psys_get_current(ob);
BMesh *bm;
DerivedMesh *dm;
if (psys && psys->part->type == PART_HAIR) {
if (!psys->hairedit) {
bm = BKE_editstrands_particles_to_bmesh(ob, psys);
if (ob->type == OB_MESH || ob->derivedFinal)
dm = ob->derivedFinal ? ob->derivedFinal : mesh_get_derived_final(eval_ctx, scene, ob, CD_MASK_BAREMESH);
else
dm = NULL;
psys->hairedit = BKE_editstrands_create(bm, dm);
}
return true;
}
return false;
}
bool ED_hair_object_apply_particle_edit(Object *ob)
{
ParticleSystem *psys = psys_get_current(ob);
if (psys->part->type == PART_HAIR) {
if (psys->hairedit) {
BKE_editstrands_particles_from_bmesh(ob, psys);
psys->flag |= PSYS_EDITED;
BKE_editstrands_free(psys->hairedit);
MEM_freeN(psys->hairedit);
psys->hairedit = NULL;
}
return true;
}
return false;
}

View File

@@ -0,0 +1,143 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) Blender Foundation
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Lukas Toenne
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/editors/hair/hair_ops.c
* \ingroup edhair
*/
#include "BLI_utildefines.h"
#include "DNA_object_types.h"
#include "BKE_context.h"
#include "RNA_access.h"
#include "WM_api.h"
#include "WM_types.h"
#include "ED_physics.h"
#include "hair_intern.h"
#include "paint_intern.h"
void ED_operatortypes_hair(void)
{
WM_operatortype_append(HAIR_OT_hair_edit_toggle);
WM_operatortype_append(HAIR_OT_select_all);
WM_operatortype_append(HAIR_OT_select_linked);
WM_operatortype_append(HAIR_OT_stroke);
}
static int hair_poll(bContext *C)
{
if (hair_edit_toggle_poll(C))
if (CTX_data_active_object(C)->mode & OB_MODE_HAIR_EDIT)
return true;
return false;
}
static void ed_keymap_hair_brush_switch(wmKeyMap *keymap, const char *mode)
{
wmKeyMapItem *kmi;
int i;
/* index 0-9 (zero key is tenth), shift key for index 10-19 */
for (i = 0; i < 20; i++) {
kmi = WM_keymap_add_item(keymap, "BRUSH_OT_active_index_set",
ZEROKEY + ((i + 1) % 10), KM_PRESS, i < 10 ? 0 : KM_SHIFT, 0);
RNA_string_set(kmi->ptr, "mode", mode);
RNA_int_set(kmi->ptr, "index", i);
}
}
static void ed_keymap_hair_brush_size(wmKeyMap *keymap, const char *UNUSED(path))
{
wmKeyMapItem *kmi;
kmi = WM_keymap_add_item(keymap, "BRUSH_OT_scale_size", LEFTBRACKETKEY, KM_PRESS, 0, 0);
RNA_float_set(kmi->ptr, "scalar", 0.9);
kmi = WM_keymap_add_item(keymap, "BRUSH_OT_scale_size", RIGHTBRACKETKEY, KM_PRESS, 0, 0);
RNA_float_set(kmi->ptr, "scalar", 10.0 / 9.0); // 1.1111....
}
static void ed_keymap_hair_brush_radial_control(wmKeyMap *keymap, const char *settings, RCFlags flags)
{
wmKeyMapItem *kmi;
/* only size needs to follow zoom, strength shows fixed size circle */
int flags_nozoom = flags & (~RC_ZOOM);
int flags_noradial_secondary = flags & (~(RC_SECONDARY_ROTATION | RC_ZOOM));
kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, 0, 0);
set_brush_rc_props(kmi->ptr, settings, "size", "use_unified_size", flags);
kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_SHIFT, 0);
set_brush_rc_props(kmi->ptr, settings, "strength", "use_unified_strength", flags_nozoom);
if (flags & RC_WEIGHT) {
kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", WKEY, KM_PRESS, 0, 0);
set_brush_rc_props(kmi->ptr, settings, "weight", "use_unified_weight", flags_nozoom);
}
if (flags & RC_ROTATION) {
kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_CTRL, 0);
set_brush_rc_props(kmi->ptr, settings, "texture_slot.angle", NULL, flags_noradial_secondary);
}
if (flags & RC_SECONDARY_ROTATION) {
kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_CTRL | KM_ALT, 0);
set_brush_rc_props(kmi->ptr, settings, "mask_texture_slot.angle", NULL, flags_nozoom);
}
}
void ED_keymap_hair(wmKeyConfig *keyconf)
{
wmKeyMap *keymap;
wmKeyMapItem *kmi;
keymap = WM_keymap_find(keyconf, "Hair", 0, 0);
keymap->poll = hair_poll;
kmi = WM_keymap_add_item(keymap, "HAIR_OT_select_all", AKEY, KM_PRESS, 0, 0);
RNA_enum_set(kmi->ptr, "action", SEL_TOGGLE);
kmi = WM_keymap_add_item(keymap, "HAIR_OT_select_all", IKEY, KM_PRESS, KM_CTRL, 0);
RNA_enum_set(kmi->ptr, "action", SEL_INVERT);
kmi = WM_keymap_add_item(keymap, "HAIR_OT_select_linked", LKEY, KM_PRESS, 0, 0);
RNA_boolean_set(kmi->ptr, "deselect", false);
kmi = WM_keymap_add_item(keymap, "HAIR_OT_select_linked", LKEY, KM_PRESS, KM_SHIFT, 0);
RNA_boolean_set(kmi->ptr, "deselect", true);
kmi = WM_keymap_add_item(keymap, "HAIR_OT_stroke", LEFTMOUSE, KM_PRESS, 0, 0);
ed_keymap_hair_brush_switch(keymap, "hair_edit");
ed_keymap_hair_brush_size(keymap, "tool_settings.hair_edit.brush.size");
ed_keymap_hair_brush_radial_control(keymap, "hair_edit", 0);
}

View File

@@ -0,0 +1,506 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) Blender Foundation
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Lukas Toenne
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/editors/hair/hair_select.c
* \ingroup edhair
*/
#include <stdlib.h>
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
#include "BLI_math.h"
#include "BLI_rect.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_view3d_types.h"
#include "BKE_context.h"
#include "BKE_editstrands.h"
#include "DEG_depsgraph.h"
#include "bmesh.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "WM_api.h"
#include "WM_types.h"
#include "ED_object.h"
#include "ED_physics.h"
#include "ED_view3d.h"
#include "hair_intern.h"
BLI_INLINE bool apply_select_action_flag(BMVert *v, int action)
{
bool cursel = BM_elem_flag_test_bool(v, BM_ELEM_SELECT);
bool newsel;
switch (action) {
case SEL_SELECT:
newsel = true;
break;
case SEL_DESELECT:
newsel = false;
break;
case SEL_INVERT:
newsel = !cursel;
break;
case SEL_TOGGLE:
/* toggle case should be converted to SELECT or DESELECT based on global state */
BLI_assert(false);
break;
}
if (newsel != cursel) {
BM_elem_flag_set(v, BM_ELEM_SELECT, newsel);
return true;
}
else
return false;
}
/* poll function */
typedef bool (*PollVertexCb)(void *userdata, struct BMVert *v);
/* distance metric function */
typedef bool (*DistanceVertexCb)(void *userdata, struct BMVert *v, float *dist);
typedef void (*ActionVertexCb)(void *userdata, struct BMVert *v, int action);
static int hair_select_verts_filter(bContext *C, Object *ob, BMEditStrands *edit,
HairEditSelectMode select_mode, int action,
PollVertexCb cb, void *userdata)
{
BMesh *bm = edit->base.bm;
BMVert *v;
BMIter iter;
int tot = 0;
bm->selectmode = BM_VERT;
switch (select_mode) {
case HAIR_SELECT_STRAND:
break;
case HAIR_SELECT_VERTEX:
BM_ITER_MESH(v, &iter, bm, BM_VERTS_OF_MESH) {
if (!cb(userdata, v))
continue;
if (apply_select_action_flag(v, action))
++tot;
}
break;
case HAIR_SELECT_TIP:
BM_ITER_MESH(v, &iter, bm, BM_VERTS_OF_MESH) {
if (!BM_strands_vert_is_tip(v))
continue;
if (!cb(userdata, v))
continue;
if (apply_select_action_flag(v, action))
++tot;
}
break;
}
BM_mesh_select_mode_flush(bm);
if (tot > 0) {
BKE_editstrands_batch_cache_dirty(edit, BKE_STRANDS_BATCH_DIRTY_SELECT);
WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob->data);
}
return tot;
}
static bool hair_select_verts_closest(bContext *C, Object *ob, BMEditStrands *edit, HairEditSelectMode select_mode, int action, DistanceVertexCb cb, ActionVertexCb action_cb, void *userdata)
{
BMesh *bm = edit->base.bm;
BMVert *v;
BMIter iter;
float dist;
BMVert *closest_v = NULL;
float closest_dist = FLT_MAX;
bm->selectmode = BM_VERT;
switch (select_mode) {
case HAIR_SELECT_STRAND:
break;
case HAIR_SELECT_VERTEX:
BM_ITER_MESH(v, &iter, bm, BM_VERTS_OF_MESH) {
if (!cb(userdata, v, &dist))
continue;
if (dist < closest_dist) {
closest_v = v;
closest_dist = dist;
}
}
break;
case HAIR_SELECT_TIP:
BM_ITER_MESH(v, &iter, bm, BM_VERTS_OF_MESH) {
if (!BM_strands_vert_is_tip(v))
continue;
if (!cb(userdata, v, &dist))
continue;
if (dist < closest_dist) {
closest_v = v;
closest_dist = dist;
}
}
break;
}
if (closest_v) {
action_cb(userdata, closest_v, action);
BM_mesh_select_mode_flush(bm);
BKE_editstrands_batch_cache_dirty(edit, BKE_STRANDS_BATCH_DIRTY_SELECT);
WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob->data);
return true;
}
else
return false;
}
static void hair_deselect_all(BMEditStrands *edit)
{
BMesh *bm = edit->base.bm;
BMVert *v;
BMIter iter;
BM_ITER_MESH(v, &iter, bm, BM_VERTS_OF_MESH) {
BM_elem_flag_set(v, BM_ELEM_SELECT, false);
}
}
/* ------------------------------------------------------------------------- */
/************************ select/deselect all operator ************************/
static bool poll_vertex_all(void *UNUSED(userdata), struct BMVert *UNUSED(v))
{
return true;
}
static int select_all_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
BMEditStrands *edit = BKE_editstrands_from_object(ob);
HairEditSettings *settings = &scene->toolsettings->hair_edit;
int action = RNA_enum_get(op->ptr, "action");
if (!edit)
return 0;
/* toggle action depends on current global selection state */
if (action == SEL_TOGGLE) {
if (edit->base.bm->totvertsel == 0)
action = SEL_SELECT;
else
action = SEL_DESELECT;
}
hair_select_verts_filter(C, ob, edit, settings->select_mode, action, poll_vertex_all, NULL);
return OPERATOR_FINISHED;
}
void HAIR_OT_select_all(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select/Deselect All";
ot->idname = "HAIR_OT_select_all";
ot->description = "Select/Deselect all hair vertices";
/* api callbacks */
ot->exec = select_all_exec;
ot->poll = hair_edit_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
WM_operator_properties_select_all(ot);
}
/************************ mouse select operator ************************/
typedef struct DistanceVertexCirleData {
ViewContext vc;
float mval[2];
float radsq;
} DistanceVertexCirleData;
static bool distance_vertex_circle(void *userdata, struct BMVert *v, float *dist)
{
DistanceVertexCirleData *data = userdata;
return hair_test_vertex_inside_circle(&data->vc, data->mval, data->radsq, v, dist);
}
static void closest_vertex_select(void *UNUSED(userdata), struct BMVert *v, int action)
{
apply_select_action_flag(v, action);
}
int ED_hair_mouse_select(bContext *C, const int mval[2], bool extend, bool deselect, bool toggle)
{
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
BMEditStrands *edit = BKE_editstrands_from_object(ob);
HairEditSettings *settings = &scene->toolsettings->hair_edit;
float select_radius = ED_view3d_select_dist_px();
DistanceVertexCirleData data;
int action;
if (!extend && !deselect && !toggle) {
hair_deselect_all(edit);
}
hair_init_viewcontext(C, &data.vc);
data.mval[0] = mval[0];
data.mval[1] = mval[1];
data.radsq = select_radius * select_radius;
if (extend)
action = SEL_SELECT;
else if (deselect)
action = SEL_DESELECT;
else
action = SEL_INVERT;
hair_select_verts_closest(C, ob, edit, settings->select_mode, action, distance_vertex_circle, closest_vertex_select, &data);
return OPERATOR_FINISHED;
}
/************************ select linked operator ************************/
static void linked_vertices_select(void *UNUSED(userdata), struct BMVert *v, int action)
{
BMVert *lv;
apply_select_action_flag(v, action);
for (lv = BM_strands_vert_prev(v); lv; lv = BM_strands_vert_prev(lv))
apply_select_action_flag(lv, action);
for (lv = BM_strands_vert_next(v); lv; lv = BM_strands_vert_next(lv))
apply_select_action_flag(lv, action);
}
static int select_linked_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
BMEditStrands *edit = BKE_editstrands_from_object(ob);
HairEditSettings *settings = &scene->toolsettings->hair_edit;
float select_radius = ED_view3d_select_dist_px();
DistanceVertexCirleData data;
int location[2];
int action;
RNA_int_get_array(op->ptr, "location", location);
hair_init_viewcontext(C, &data.vc);
data.mval[0] = location[0];
data.mval[1] = location[1];
data.radsq = select_radius * select_radius;
action = RNA_boolean_get(op->ptr, "deselect") ? SEL_DESELECT : SEL_SELECT;
hair_select_verts_closest(C, ob, edit, settings->select_mode, action, distance_vertex_circle, linked_vertices_select, &data);
return OPERATOR_FINISHED;
}
static int select_linked_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
RNA_int_set_array(op->ptr, "location", event->mval);
return select_linked_exec(C, op);
}
void HAIR_OT_select_linked(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Linked";
ot->idname = "HAIR_OT_select_linked";
ot->description = "Select connected vertices";
/* api callbacks */
ot->exec = select_linked_exec;
ot->invoke = select_linked_invoke;
ot->poll = hair_edit_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "Deselect linked keys rather than selecting them");
RNA_def_int_vector(ot->srna, "location", 2, NULL, 0, INT_MAX, "Location", "", 0, 16384);
}
/************************ border select operator ************************/
typedef struct PollVertexRectData {
ViewContext vc;
rcti rect;
} PollVertexRectData;
static bool poll_vertex_inside_rect(void *userdata, struct BMVert *v)
{
PollVertexRectData *data = userdata;
return hair_test_vertex_inside_rect(&data->vc, &data->rect, v);
}
int ED_hair_border_select(bContext *C, rcti *rect, bool select, bool extend)
{
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
BMEditStrands *edit = BKE_editstrands_from_object(ob);
HairEditSettings *settings = &scene->toolsettings->hair_edit;
PollVertexRectData data;
int action;
if (!extend && select)
hair_deselect_all(edit);
hair_init_viewcontext(C, &data.vc);
data.rect = *rect;
if (extend)
action = SEL_SELECT;
else if (select)
action = SEL_INVERT;
else
action = SEL_DESELECT;
hair_select_verts_filter(C, ob, edit, settings->select_mode, action, poll_vertex_inside_rect, &data);
return OPERATOR_FINISHED;
}
/************************ circle select operator ************************/
typedef struct PollVertexCirleData {
ViewContext vc;
float mval[2];
float radsq;
} PollVertexCirleData;
static bool poll_vertex_inside_circle(void *userdata, struct BMVert *v)
{
PollVertexCirleData *data = userdata;
float dist;
return hair_test_vertex_inside_circle(&data->vc, data->mval, data->radsq, v, &dist);
}
int ED_hair_circle_select(bContext *C, bool select, const int mval[2], float radius)
{
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
BMEditStrands *edit = BKE_editstrands_from_object(ob);
HairEditSettings *settings = &scene->toolsettings->hair_edit;
int action = select ? SEL_SELECT : SEL_DESELECT;
PollVertexCirleData data;
int tot;
if (!edit)
return 0;
hair_init_viewcontext(C, &data.vc);
data.mval[0] = mval[0];
data.mval[1] = mval[1];
data.radsq = radius * radius;
tot = hair_select_verts_filter(C, ob, edit, settings->select_mode, action, poll_vertex_inside_circle, &data);
return tot;
}
/************************ lasso select operator ************************/
typedef struct PollVertexLassoData {
ViewContext vc;
const int (*mcoords)[2];
short moves;
} PollVertexLassoData;
static bool poll_vertex_inside_lasso(void *userdata, struct BMVert *v)
{
PollVertexLassoData *data = userdata;
return hair_test_vertex_inside_lasso(&data->vc, data->mcoords, data->moves, v);
}
int ED_hair_lasso_select(bContext *C, const int mcoords[][2], const short moves, bool extend, bool select)
{
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
BMEditStrands *edit = BKE_editstrands_from_object(ob);
HairEditSettings *settings = &scene->toolsettings->hair_edit;
PollVertexLassoData data;
int action;
if (!extend && select)
hair_deselect_all(edit);
hair_init_viewcontext(C, &data.vc);
data.mcoords = mcoords;
data.moves = moves;
if (extend)
action = SEL_SELECT;
else if (select)
action = SEL_INVERT;
else
action = SEL_DESELECT;
hair_select_verts_filter(C, ob, edit, settings->select_mode, action, poll_vertex_inside_lasso, &data);
return OPERATOR_FINISHED;
}

View File

@@ -0,0 +1,501 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) Blender Foundation
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Lukas Toenne
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/editors/hair/hair_edit.c
* \ingroup edhair
*/
#include <stdlib.h>
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
#include "BLI_lasso.h"
#include "BLI_math.h"
#include "BLI_rect.h"
#include "DNA_brush_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_view3d_types.h"
#include "BKE_brush.h"
#include "BKE_DerivedMesh.h"
#include "BKE_editstrands.h"
#include "BKE_effect.h"
#include "BKE_mesh_sample.h"
#include "bmesh.h"
#include "BIF_gl.h"
#include "BIF_glutil.h"
#include "ED_physics.h"
#include "ED_view3d.h"
#include "hair_intern.h"
bool hair_test_depth(ViewContext *vc, const float co[3], const int screen_co[2])
{
View3D *v3d = vc->v3d;
ViewDepths *vd = vc->rv3d->depths;
const bool has_zbuf = (v3d->drawtype > OB_WIRE) && (v3d->flag & V3D_ZBUF_SELECT);
float uco[3];
float depth;
/* nothing to do */
if (!has_zbuf)
return true;
ED_view3d_project(vc->ar, co, uco);
/* check if screen_co is within bounds because brush_cut uses out of screen coords */
if (screen_co[0] >= 0 && screen_co[0] < vd->w && screen_co[1] >= 0 && screen_co[1] < vd->h) {
BLI_assert(vd && vd->depths);
/* we know its not clipped */
depth = vd->depths[screen_co[1] * vd->w + screen_co[0]];
return ((float)uco[2] - 0.00001f <= depth);
}
return false;
}
bool hair_test_vertex_inside_circle(ViewContext *vc, const float mval[2], float radsq, BMVert *v, float *r_dist)
{
float (*obmat)[4] = vc->obact->obmat;
float co_world[3];
float dx, dy, distsq;
int screen_co[2];
mul_v3_m4v3(co_world, obmat, v->co);
/* TODO, should this check V3D_PROJ_TEST_CLIP_BB too? */
if (ED_view3d_project_int_global(vc->ar, co_world, screen_co, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK)
return false;
dx = mval[0] - (float)screen_co[0];
dy = mval[1] - (float)screen_co[1];
distsq = dx * dx + dy * dy;
if (distsq > radsq)
return false;
if (hair_test_depth(vc, v->co, screen_co)) {
*r_dist = sqrtf(distsq);
return true;
}
else
return false;
}
bool hair_test_edge_inside_circle(ViewContext *vc, const float mval[2], float radsq, BMVert *v1, BMVert *v2, float *r_dist, float *r_lambda)
{
float (*obmat)[4] = vc->obact->obmat;
float world_co1[3], world_co2[3];
float dx, dy, distsq;
int screen_co1[2], screen_co2[2], screen_cp[2];
float lambda, world_cp[3], screen_cpf[2], screen_co1f[2], screen_co2f[2];
mul_v3_m4v3(world_co1, obmat, v1->co);
mul_v3_m4v3(world_co2, obmat, v2->co);
/* TODO, should this check V3D_PROJ_TEST_CLIP_BB too? */
if (ED_view3d_project_int_global(vc->ar, world_co1, screen_co1, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK)
return false;
if (ED_view3d_project_int_global(vc->ar, world_co2, screen_co2, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK)
return false;
screen_co1f[0] = screen_co1[0];
screen_co1f[1] = screen_co1[1];
screen_co2f[0] = screen_co2[0];
screen_co2f[1] = screen_co2[1];
lambda = closest_to_line_v2(screen_cpf, mval, screen_co1f, screen_co2f);
if (lambda < 0.0f || lambda > 1.0f) {
CLAMP(lambda, 0.0f, 1.0f);
interp_v2_v2v2(screen_cpf, screen_co1f, screen_co2f, lambda);
}
dx = mval[0] - screen_cpf[0];
dy = mval[1] - screen_cpf[1];
distsq = dx * dx + dy * dy;
if (distsq > radsq)
return false;
interp_v3_v3v3(world_cp, world_co1, world_co2, lambda);
screen_cp[0] = screen_cpf[0];
screen_cp[1] = screen_cpf[1];
if (hair_test_depth(vc, world_cp, screen_cp)) {
*r_dist = sqrtf(distsq);
*r_lambda = lambda;
return true;
}
else
return false;
}
bool hair_test_vertex_inside_rect(ViewContext *vc, rcti *rect, BMVert *v)
{
float (*obmat)[4] = vc->obact->obmat;
float co_world[3];
int screen_co[2];
mul_v3_m4v3(co_world, obmat, v->co);
/* TODO, should this check V3D_PROJ_TEST_CLIP_BB too? */
if (ED_view3d_project_int_global(vc->ar, co_world, screen_co, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK)
return false;
if (!BLI_rcti_isect_pt_v(rect, screen_co))
return false;
if (hair_test_depth(vc, v->co, screen_co))
return true;
else
return false;
}
bool hair_test_vertex_inside_lasso(ViewContext *vc, const int mcoords[][2], short moves, BMVert *v)
{
float (*obmat)[4] = vc->obact->obmat;
float co_world[3];
int screen_co[2];
mul_v3_m4v3(co_world, obmat, v->co);
/* TODO, should this check V3D_PROJ_TEST_CLIP_BB too? */
if (ED_view3d_project_int_global(vc->ar, co_world, screen_co, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK)
return false;
if (!BLI_lasso_is_point_inside(mcoords, moves, screen_co[0], screen_co[1], IS_CLIPPED))
return false;
if (hair_test_depth(vc, v->co, screen_co))
return true;
else
return false;
}
/* ------------------------------------------------------------------------- */
typedef void (*VertexToolCb)(HairToolData *data, void *userdata, BMVert *v, float factor);
/* apply tool directly to each vertex inside the filter area */
static int UNUSED_FUNCTION(hair_tool_apply_vertex)(HairToolData *data, VertexToolCb cb, void *userdata)
{
BMesh *bm = data->edit->base.bm;
Scene *scene = data->scene;
Brush *brush = data->settings->brush;
const float rad = BKE_brush_size_get(scene, brush);
const float radsq = rad*rad;
const float threshold = 0.0f; /* XXX could be useful, is it needed? */
const bool use_mirror = hair_use_mirror_x(data->ob);
BMVert *v, *v_mirr;
BMIter iter;
int tot = 0;
float dist, factor;
if (use_mirror)
ED_strands_mirror_cache_begin(data->edit, 0, false, false, hair_use_mirror_topology(data->ob));
BM_ITER_MESH(v, &iter, bm, BM_VERTS_OF_MESH) {
if (!hair_test_vertex_inside_circle(&data->vc, data->mval, radsq, v, &dist))
continue;
factor = 1.0f - dist / rad;
if (factor > threshold) {
cb(data, userdata, v, factor);
++tot;
if (use_mirror) {
v_mirr = ED_strands_mirror_get(data->edit, v);
if (v_mirr)
cb(data, userdata, v_mirr, factor);
}
}
}
/* apply mirror */
if (use_mirror)
ED_strands_mirror_cache_end(data->edit);
return tot;
}
/* ------------------------------------------------------------------------- */
typedef void (*EdgeToolCb)(HairToolData *data, void *userdata, BMVert *v1, BMVert *v2, float factor, float edge_param);
static int hair_tool_apply_strand_edges(HairToolData *data, EdgeToolCb cb, void *userdata, BMVert *root)
{
Scene *scene = data->scene;
Brush *brush = data->settings->brush;
const float rad = BKE_brush_size_get(scene, brush);
const float radsq = rad*rad;
const float threshold = 0.0f; /* XXX could be useful, is it needed? */
const bool use_mirror = hair_use_mirror_x(data->ob);
BMVert *v, *v_mirr, *vprev, *vprev_mirr;
BMIter iter;
int k;
int tot = 0;
BM_ITER_STRANDS_ELEM_INDEX(v, &iter, root, BM_VERTS_OF_STRAND, k) {
if (use_mirror)
v_mirr = ED_strands_mirror_get(data->edit, v);
if (k > 0) {
float dist, lambda;
if (hair_test_edge_inside_circle(&data->vc, data->mval, radsq, vprev, v, &dist, &lambda)) {
float factor = 1.0f - dist / rad;
if (factor > threshold) {
cb(data, userdata, vprev, v, factor, lambda);
++tot;
if (use_mirror) {
if (vprev_mirr && v_mirr)
cb(data, userdata, vprev_mirr, v_mirr, factor, lambda);
}
}
}
}
vprev = v;
vprev_mirr = v_mirr;
}
return tot;
}
/* apply tool to vertices of edges inside the filter area,
* using the closest edge point for weighting
*/
static int hair_tool_apply_edge(HairToolData *data, EdgeToolCb cb, void *userdata)
{
BMesh *bm = data->edit->base.bm;
BMVert *root;
BMIter iter;
int tot = 0;
if (hair_use_mirror_x(data->ob))
ED_strands_mirror_cache_begin(data->edit, 0, false, false, hair_use_mirror_topology(data->ob));
BM_ITER_STRANDS(root, &iter, bm, BM_STRANDS_OF_MESH) {
tot += hair_tool_apply_strand_edges(data, cb, userdata, root);
}
/* apply mirror */
if (hair_use_mirror_x(data->ob))
ED_strands_mirror_cache_end(data->edit);
return tot;
}
/* ------------------------------------------------------------------------- */
typedef struct CombData {
float power;
} CombData;
static void UNUSED_FUNCTION(hair_vertex_comb)(HairToolData *data, void *userdata, BMVert *v, float factor)
{
CombData *combdata = userdata;
float combfactor = powf(factor, combdata->power);
madd_v3_v3fl(v->co, data->delta, combfactor);
}
/* Edge-based combing tool:
* Unlike the vertex tool (which simply displaces vertices), the edge tool
* adjusts edge orientations to follow the stroke direction.
*/
static void hair_edge_comb(HairToolData *data, void *userdata, BMVert *v1, BMVert *v2, float factor, float UNUSED(edge_param))
{
CombData *combdata = userdata;
float strokedir[3], edge[3], edgedir[3], strokelen, edgelen;
float edge_proj[3];
float combfactor = powf(factor, combdata->power);
float effect;
strokelen = normalize_v3_v3(strokedir, data->delta);
sub_v3_v3v3(edge, v2->co, v1->co);
edgelen = normalize_v3_v3(edgedir, edge);
if (edgelen == 0.0f)
return;
/* This factor prevents sudden changes in direction with small stroke lengths.
* The arctan maps the 0..inf range of the length ratio to 0..1 smoothly.
*/
effect = atan(strokelen / edgelen * 4.0f / (0.5f*M_PI));
mul_v3_v3fl(edge_proj, strokedir, edgelen);
interp_v3_v3v3(edge, edge, edge_proj, combfactor * effect);
add_v3_v3v3(v2->co, v1->co, edge);
}
BLI_INLINE void construct_m4_loc_nor_tan(float mat[4][4], const float loc[3], const float nor[3], const float tang[3])
{
float cotang[3];
cross_v3_v3v3(cotang, nor, tang);
copy_v3_v3(mat[0], tang);
copy_v3_v3(mat[1], cotang);
copy_v3_v3(mat[2], nor);
copy_v3_v3(mat[3], loc);
mat[0][3] = 0.0f;
mat[1][3] = 0.0f;
mat[2][3] = 0.0f;
mat[3][3] = 1.0f;
}
static void grow_hair(BMEditStrands *edit, MeshSample *sample)
{
BMesh *bm = edit->base.bm;
DerivedMesh *dm = edit->root_dm;
const float len = 1.5f;
float root_mat[4][4];
BMVert *root, *v;
BMIter iter;
int i;
{
float co[3], nor[3], tang[3];
BKE_mesh_sample_eval(dm, sample, co, nor, tang);
construct_m4_loc_nor_tan(root_mat, co, nor, tang);
}
root = BM_strands_create(bm, 5, true);
BM_elem_meshsample_data_named_set(&bm->vdata, root, CD_MSURFACE_SAMPLE, CD_HAIR_ROOT_LOCATION, sample);
BM_ITER_STRANDS_ELEM_INDEX(v, &iter, root, BM_VERTS_OF_STRAND, i) {
float co[3];
co[0] = co[1] = 0.0f;
co[2] = len * (float)i / (float)(len - 1);
mul_m4_v3(root_mat, co);
copy_v3_v3(v->co, co);
}
BM_mesh_elem_index_ensure(bm, BM_ALL);
}
static bool hair_add_ray_cb(void *vdata, void *UNUSED(thread_ctx), float ray_start[3], float ray_end[3])
{
HairToolData *data = vdata;
ViewContext *vc = &data->vc;
ED_view3d_win_to_segment(vc->ar, vc->v3d, data->mval, ray_start, ray_end, true);
mul_m4_v3(data->imat, ray_start);
mul_m4_v3(data->imat, ray_end);
return true;
}
static bool hair_get_surface_sample(HairToolData *data, MeshSample *sample)
{
DerivedMesh *dm = data->edit->root_dm;
MeshSampleGenerator *gen;
bool ok;
gen = BKE_mesh_sample_gen_surface_raycast(NULL, NULL, hair_add_ray_cb, data);
BKE_mesh_sample_generator_bind(gen, dm);
ok = BKE_mesh_sample_generate(gen, sample);
BKE_mesh_sample_free_generator(gen);
return ok;
}
static bool hair_add(HairToolData *data)
{
MeshSample sample;
if (!hair_get_surface_sample(data, &sample))
return false;
grow_hair(data->edit, &sample);
return true;
}
bool hair_brush_step(HairToolData *data)
{
Brush *brush = data->settings->brush;
BrushHairTool hair_tool = brush->hair_tool;
BMEditStrands *edit = data->edit;
int tot = 0;
switch (hair_tool) {
case HAIR_TOOL_COMB: {
CombData combdata;
combdata.power = (brush->alpha - 0.5f) * 2.0f;
if (combdata.power < 0.0f)
combdata.power = 1.0f - 9.0f * combdata.power;
else
combdata.power = 1.0f - combdata.power;
tot = hair_tool_apply_edge(data, hair_edge_comb, &combdata);
break;
}
case HAIR_TOOL_CUT:
break;
case HAIR_TOOL_LENGTH:
break;
case HAIR_TOOL_PUFF:
break;
case HAIR_TOOL_ADD:
if (hair_add(data))
edit->flag |= BM_STRANDS_DIRTY_SEGLEN;
break;
case HAIR_TOOL_SMOOTH:
break;
case HAIR_TOOL_WEIGHT:
break;
}
return tot > 0;
}

View File

@@ -0,0 +1,195 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) Blender Foundation
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Lukas Toenne
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/editors/hair/hair_undo.c
* \ingroup edhair
*/
#include <stdlib.h>
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_view3d_types.h"
#include "BKE_context.h"
#include "BKE_DerivedMesh.h"
#include "BKE_editstrands.h"
#include "BKE_key.h"
#include "BKE_mesh.h"
#include "BKE_mesh_sample.h"
#include "ED_physics.h"
#include "ED_util.h"
#include "bmesh.h"
#include "hair_intern.h"
static void *strands_get_edit(bContext *C)
{
Object *obact = CTX_data_active_object(C);
const int mode_flag = OB_MODE_HAIR_EDIT;
const bool is_mode_set = ((obact->mode & mode_flag) != 0);
if (obact && is_mode_set) {
BMEditStrands *edit = BKE_editstrands_from_object(obact);
return edit;
}
return NULL;
}
typedef struct UndoStrands {
Mesh me; /* Mesh supports all the customdata we need, easiest way to implement undo storage */
int selectmode;
/** \note
* this isn't a prefect solution, if you edit keys and change shapes this works well (fixing [#32442]),
* but editing shape keys, going into object mode, removing or changing their order,
* then go back into editmode and undo will give issues - where the old index will be out of sync
* with the new object index.
*
* There are a few ways this could be made to work but for now its a known limitation with mixing
* object and editmode operations - Campbell */
int shapenr;
} UndoStrands;
/* undo simply makes copies of a bmesh */
static void *strands_edit_to_undo(void *editv, void *UNUSED(obdata))
{
BMEditStrands *edit = editv;
BMesh *bm = edit->base.bm;
// Mesh *obme = obdata;
struct BMeshToMeshParams params = {0};
UndoStrands *undo = MEM_callocN(sizeof(UndoStrands), "undo Strands");
/* make sure shape keys work */
// um->me.key = obme->key ? BKE_key_copy_nolib(obme->key) : NULL;
/* BM_mesh_validate(em->bm); */ /* for troubleshooting */
params.cd_mask_extra = CD_MASK_STRANDS;
BM_mesh_bm_to_me(bm, &undo->me, &params);
undo->selectmode = bm->selectmode;
undo->shapenr = bm->shapenr;
return undo;
}
static void strands_undo_to_edit(void *undov, void *editv, void *UNUSED(obdata))
{
UndoStrands *undo = undov;
BMEditStrands *edit = editv, *edit_tmp;
Object *ob = edit->base.ob;
DerivedMesh *dm = edit->root_dm;
BMesh *bm;
// Key *key = ((Mesh *) obdata)->key;
struct BMeshFromMeshParams params = {0};
const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(&undo->me);
edit->base.bm->shapenr = undo->shapenr;
bm = BM_mesh_create(&allocsize,
&((struct BMeshCreateParams){.use_toolflags = false,}));
params.cd_mask_extra = CD_MASK_STRANDS_BMESH;
params.active_shapekey = undo->shapenr;
BM_mesh_bm_from_me(bm, &undo->me, &params);
/* note: have to create the new edit before freeing the old one,
* because it owns the root_dm and we have to copy it before
* it gets released when freeing the old edit.
*/
edit_tmp = BKE_editstrands_create(bm, dm);
BKE_editstrands_free(edit);
*edit = *edit_tmp;
bm->selectmode = undo->selectmode;
edit->base.ob = ob;
#if 0
/* T35170: Restore the active key on the RealMesh. Otherwise 'fake' offset propagation happens
* if the active is a basis for any other. */
if (key && (key->type == KEY_RELATIVE)) {
/* Since we can't add, remove or reorder keyblocks in editmode, it's safe to assume
* shapenr from restored bmesh and keyblock indices are in sync. */
const int kb_act_idx = ob->shapenr - 1;
/* If it is, let's patch the current mesh key block to its restored value.
* Else, the offsets won't be computed and it won't matter. */
if (BKE_keyblock_is_basis(key, kb_act_idx)) {
KeyBlock *kb_act = BLI_findlink(&key->block, kb_act_idx);
if (kb_act->totelem != undo->me.totvert) {
/* The current mesh has some extra/missing verts compared to the undo, adjust. */
MEM_SAFE_FREE(kb_act->data);
kb_act->data = MEM_mallocN((size_t)(key->elemsize * bm->totvert), __func__);
kb_act->totelem = undo->me.totvert;
}
BKE_keyblock_update_from_mesh(&undo->me, kb_act);
}
}
#endif
ob->shapenr = undo->shapenr;
MEM_freeN(edit_tmp);
}
static void strands_free_undo(void *undov)
{
UndoStrands *undo = undov;
if (undo->me.key) {
BKE_key_free(undo->me.key);
MEM_freeN(undo->me.key);
}
BKE_mesh_free(&undo->me);
MEM_freeN(undo);
}
/* and this is all the undo system needs to know */
void undo_push_strands(bContext *C, const char *name)
{
/* edit->ob gets out of date and crashes on mesh undo,
* this is an easy way to ensure its OK
* though we could investigate the matter further. */
Object *obact = CTX_data_active_object(C);
BMEditStrands *edit = BKE_editstrands_from_object(obact);
edit->base.ob = obact;
undo_editmode_push(C, name, CTX_data_active_object, strands_get_edit, strands_free_undo, strands_undo_to_edit, strands_edit_to_undo, NULL);
}

View File

@@ -165,6 +165,27 @@ extern char datatoc_twist_png[];
extern int datatoc_vertexdraw_png_size;
extern char datatoc_vertexdraw_png[];
extern int datatoc_haircomb_png_size;
extern char datatoc_haircomb_png[];
extern int datatoc_haircut_png_size;
extern char datatoc_haircut_png[];
extern int datatoc_hairlength_png_size;
extern char datatoc_hairlength_png[];
extern int datatoc_hairpuff_png_size;
extern char datatoc_hairpuff_png[];
extern int datatoc_hairadd_png_size;
extern char datatoc_hairadd_png[];
extern int datatoc_hairsmooth_png_size;
extern char datatoc_hairsmooth_png[];
extern int datatoc_hairweight_png_size;
extern char datatoc_hairweight_png[];
/* Matcap files */
extern int datatoc_mc01_jpg_size;

View File

@@ -35,9 +35,13 @@
struct bContext;
struct ReportList;
struct wmKeyConfig;
struct ViewContext;
struct rcti;
struct Main;
struct Scene;
struct Object;
struct BMEditStrands;
/* particle_edit.c */
int PE_poll(struct bContext *C);
@@ -56,5 +60,28 @@ void ED_rigidbody_constraint_remove(struct Main *bmain, struct Scene *scene, str
void ED_operatortypes_physics(void);
void ED_keymap_physics(struct wmKeyConfig *keyconf);
/* hair edit */
void undo_push_strands(struct bContext *C, const char *name);
void ED_strands_mirror_cache_begin_ex(struct BMEditStrands *edit, const int axis,
const bool use_self, const bool use_select,
const bool use_topology, float maxdist, int *r_index);
void ED_strands_mirror_cache_begin(struct BMEditStrands *edit, const int axis,
const bool use_self, const bool use_select,
const bool use_topology);
void ED_strands_mirror_apply(struct BMEditStrands *edit, const int sel_from, const int sel_to);
struct BMVert *ED_strands_mirror_get(struct BMEditStrands *edit, struct BMVert *v);
struct BMEdge *ED_strands_mirror_get_edge(struct BMEditStrands *edit, struct BMEdge *e);
void ED_strands_mirror_cache_clear(struct BMEditStrands *edit, struct BMVert *v);
void ED_strands_mirror_cache_end(struct BMEditStrands *edit);
int ED_hair_mouse_select(struct bContext *C, const int mval[2], bool extend, bool deselect, bool toggle);
int ED_hair_border_select(struct bContext *C, struct rcti *rect, bool select, bool extend);
int ED_hair_circle_select(struct bContext *C, bool select, const int mval[2], float radius);
int ED_hair_lasso_select(struct bContext *C, const int mcoords[][2], short moves, bool extend, bool select);
void ED_operatortypes_hair(void);
void ED_keymap_hair(struct wmKeyConfig *keyconf);
#endif /* __ED_PHYSICS_H__ */

View File

@@ -32,6 +32,8 @@
#define __ED_UTIL_H__
struct bContext;
struct PackedFile;
struct ScrArea;
struct SpaceLink;
struct wmOperator;
struct wmOperatorType;
@@ -72,6 +74,7 @@ bool ED_undo_is_valid(const struct bContext *C, const char *undoname);
/* undo_editmode.c */
void undo_editmode_push(struct bContext *C, const char *name,
struct Object *(*get_object)(const struct bContext * C),
void * (*getdata)(struct bContext *C),
void (*freedata)(void *),
void (*to_editmode)(void *, void *, void *),

View File

@@ -977,6 +977,13 @@ DEF_ICON(BRUSH_TEXMASK)
DEF_ICON(BRUSH_THUMB)
DEF_ICON(BRUSH_ROTATE)
DEF_ICON(BRUSH_VERTEXDRAW)
DEF_ICON(BRUSH_HAIR_COMB)
DEF_ICON(BRUSH_HAIR_CUT)
DEF_ICON(BRUSH_HAIR_LENGTH)
DEF_ICON(BRUSH_HAIR_PUFF)
DEF_ICON(BRUSH_HAIR_ADD)
DEF_ICON(BRUSH_HAIR_SMOOTH)
DEF_ICON(BRUSH_HAIR_WEIGHT)
/* Matcaps */
DEF_ICON(MATCAP_01)

View File

@@ -415,6 +415,13 @@ static void init_brush_icons(void)
INIT_BRUSH_ICON(ICON_BRUSH_THUMB, thumb);
INIT_BRUSH_ICON(ICON_BRUSH_ROTATE, twist);
INIT_BRUSH_ICON(ICON_BRUSH_VERTEXDRAW, vertexdraw);
INIT_BRUSH_ICON(ICON_BRUSH_HAIR_COMB, haircomb);
INIT_BRUSH_ICON(ICON_BRUSH_HAIR_CUT, haircut);
INIT_BRUSH_ICON(ICON_BRUSH_HAIR_LENGTH, hairlength);
INIT_BRUSH_ICON(ICON_BRUSH_HAIR_PUFF, hairpuff);
INIT_BRUSH_ICON(ICON_BRUSH_HAIR_ADD, hairadd);
INIT_BRUSH_ICON(ICON_BRUSH_HAIR_SMOOTH, hairsmooth);
INIT_BRUSH_ICON(ICON_BRUSH_HAIR_WEIGHT, hairweight);
#undef INIT_BRUSH_ICON
}
@@ -1209,6 +1216,8 @@ static int ui_id_brush_get_icon(const bContext *C, ID *id)
mode = OB_MODE_VERTEX_PAINT;
else if (ob->mode & OB_MODE_TEXTURE_PAINT)
mode = OB_MODE_TEXTURE_PAINT;
else if (ob->mode & OB_MODE_HAIR_EDIT)
mode = OB_MODE_HAIR_EDIT;
}
else if ((sima = CTX_wm_space_image(C)) &&
(sima->mode == SI_MODE_PAINT))
@@ -1229,6 +1238,10 @@ static int ui_id_brush_get_icon(const bContext *C, ID *id)
items = rna_enum_brush_image_tool_items;
tool = br->imagepaint_tool;
}
else if (mode == OB_MODE_HAIR_EDIT) {
items = brush_hair_tool_items;
tool = br->hair_tool;
}
if (!items || !RNA_enum_icon_from_value(items, tool, &id->icon_id))
id->icon_id = 0;

View File

@@ -665,5 +665,5 @@ void undo_push_mesh(bContext *C, const char *name)
BMEditMesh *em = BKE_editmesh_from_object(obedit);
em->ob = obedit;
undo_editmode_push(C, name, getEditMesh, free_undo, undoMesh_to_editbtMesh, editbtMesh_to_undoMesh, NULL);
undo_editmode_push(C, name, CTX_data_edit_object, getEditMesh, free_undo, undoMesh_to_editbtMesh, editbtMesh_to_undoMesh, NULL);
}

View File

@@ -752,5 +752,5 @@ static void *get_data(bContext *C)
/* this is undo system for MetaBalls */
void undo_push_mball(bContext *C, const char *name)
{
undo_editmode_push(C, name, get_data, free_undoMball, undoMball_to_editMball, editMball_to_undoMball, NULL);
undo_editmode_push(C, name, CTX_data_edit_object, get_data, free_undoMball, undoMball_to_editMball, editMball_to_undoMball, NULL);
}

View File

@@ -49,6 +49,7 @@ set(SRC
object_edit.c
object_facemap_ops.c
object_group.c
object_hair.c
object_hook.c
object_lattice.c
object_lod.c

View File

@@ -1411,7 +1411,7 @@ static EnumPropertyItem *object_mode_set_itemsf(bContext *C, PointerRNA *UNUSED(
(input->value == OB_MODE_POSE && (ob->type == OB_ARMATURE)) ||
(input->value == OB_MODE_PARTICLE_EDIT && use_mode_particle_edit) ||
(ELEM(input->value, OB_MODE_SCULPT, OB_MODE_VERTEX_PAINT,
OB_MODE_WEIGHT_PAINT, OB_MODE_TEXTURE_PAINT) && (ob->type == OB_MESH)) ||
OB_MODE_WEIGHT_PAINT, OB_MODE_TEXTURE_PAINT, OB_MODE_HAIR_EDIT) && (ob->type == OB_MESH)) ||
(input->value == OB_MODE_OBJECT))
{
RNA_enum_item_add(&item, &totitem, input);
@@ -1453,6 +1453,8 @@ static const char *object_mode_op_string(int mode)
return "PAINT_OT_texture_paint_toggle";
if (mode == OB_MODE_PARTICLE_EDIT)
return "PARTICLE_OT_particle_edit_toggle";
if (mode == OB_MODE_HAIR_EDIT)
return "HAIR_OT_hair_edit_toggle";
if (mode == OB_MODE_POSE)
return "OBJECT_OT_posemode_toggle";
if (mode == OB_MODE_GPENCIL)
@@ -1474,7 +1476,7 @@ static bool object_mode_compat_test(Object *ob, ObjectMode mode)
switch (ob->type) {
case OB_MESH:
if (mode & (OB_MODE_EDIT | OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT |
OB_MODE_TEXTURE_PAINT | OB_MODE_PARTICLE_EDIT))
OB_MODE_TEXTURE_PAINT | OB_MODE_PARTICLE_EDIT | OB_MODE_HAIR_EDIT))
{
return true;
}

View File

@@ -0,0 +1,117 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) Blender Foundation
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Lukas Toenne
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/editors/object/object_hair.c
* \ingroup edobj
*/
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
#include "DNA_hair_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "BKE_context.h"
#include "BKE_DerivedMesh.h"
#include "BKE_hair.h"
#include "DEG_depsgraph.h"
#include "ED_object.h"
#include "WM_api.h"
#include "WM_types.h"
#include "object_intern.h"
/************************ Hair Follicle Generation Operator *********************/
static int hair_follicles_generate_poll(bContext *C)
{
return edit_modifier_poll_generic(C, &RNA_HairModifier, 0);
}
static int hair_follicles_generate_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Object *ob = ED_object_active_context(C);
HairModifierData *hmd = (HairModifierData *)edit_modifier_property_get(op, ob, eModifierType_Hair);
EvaluationContext eval_ctx;
CTX_data_eval_ctx(C, &eval_ctx);
if (!hmd)
return OPERATOR_CANCELLED;
CustomDataMask mask = CD_MASK_BAREMESH;
DerivedMesh *scalp = mesh_get_derived_final(&eval_ctx, scene, ob, mask);
if (!scalp)
return OPERATOR_CANCELLED;
int count = RNA_int_get(op->ptr, "count");
unsigned int seed = RNA_int_get(op->ptr, "seed");
BKE_hair_follicles_generate(hmd->hair, scalp, count, seed);
DEG_id_tag_update(&ob->id, OB_RECALC_DATA);
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
return OPERATOR_FINISHED;
}
static int hair_follicles_generate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
if (edit_modifier_invoke_properties(C, op)) {
return WM_operator_props_popup_confirm(C, op, event);
}
else {
return OPERATOR_CANCELLED;
}
}
void OBJECT_OT_hair_follicles_generate(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Generate Hair Follicles";
ot->description = "Generate hair follicle data";
ot->idname = "OBJECT_OT_hair_follicles_generate";
/* api callbacks */
ot->poll = hair_follicles_generate_poll;
ot->invoke = hair_follicles_generate_invoke;
ot->exec = hair_follicles_generate_exec;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
edit_modifier_properties(ot);
RNA_def_int(ot->srna, "count", 1000, 0, INT_MAX, "Count", "Number of hair follicles to generate", 1, 1000000);
RNA_def_int(ot->srna, "seed", 0, 0, INT_MAX, "Seed", "Seed value for randomization", 0, INT_MAX);
}

View File

@@ -286,5 +286,8 @@ void TRANSFORM_OT_vertex_random(struct wmOperatorType *ot);
void OBJECT_OT_data_transfer(struct wmOperatorType *ot);
void OBJECT_OT_datalayout_transfer(struct wmOperatorType *ot);
/* object_hair.c */
void OBJECT_OT_hair_follicles_generate(struct wmOperatorType *ot);
#endif /* __OBJECT_INTERN_H__ */

View File

@@ -982,6 +982,6 @@ static void *get_editlatt(bContext *C)
/* and this is all the undo system needs to know */
void undo_push_lattice(bContext *C, const char *name)
{
undo_editmode_push(C, name, get_editlatt, free_undoLatt, undoLatt_to_editLatt, editLatt_to_undoLatt, validate_undoLatt);
undo_editmode_push(C, name, CTX_data_edit_object, get_editlatt, free_undoLatt, undoLatt_to_editLatt, editLatt_to_undoLatt, validate_undoLatt);
}

View File

@@ -260,6 +260,8 @@ void ED_operatortypes_object(void)
WM_operatortype_append(OBJECT_OT_data_transfer);
WM_operatortype_append(OBJECT_OT_datalayout_transfer);
WM_operatortype_append(OBJECT_OT_surfacedeform_bind);
WM_operatortype_append(OBJECT_OT_hair_follicles_generate);
}
void ED_operatormacros_object(void)

View File

@@ -72,7 +72,7 @@ const char *screen_context_dir[] = {
"visible_pose_bones", "selected_pose_bones", "active_bone", "active_pose_bone",
"active_base", "active_object", "object", "edit_object",
"sculpt_object", "vertex_paint_object", "weight_paint_object",
"image_paint_object", "particle_edit_object",
"image_paint_object", "particle_edit_object", "hair_edit_object",
"sequences", "selected_sequences", "selected_editable_sequences", /* sequencer */
"gpencil_data", "gpencil_data_owner", /* grease pencil data */
"visible_gpencil_layers", "editable_gpencil_layers", "editable_gpencil_strokes",
@@ -399,6 +399,12 @@ int ed_screen_context(const bContext *C, const char *member, bContextDataResult
return 1;
}
else if (CTX_data_equals(member, "hair_edit_object")) {
if (obact && (obact->mode & OB_MODE_HAIR_EDIT))
CTX_data_id_pointer_set(result, &obact->id);
return 1;
}
else if (CTX_data_equals(member, "sequences")) {
Editing *ed = BKE_sequencer_editing_get(scene, false);
if (ed) {

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