Compare commits

...

192 Commits

Author SHA1 Message Date
8321ee3b83 Cleanup: Use int64_t for size(). 2022-11-18 10:51:12 +01:00
2d5d96088f Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-11-18 10:46:14 +01:00
920f8449aa Updated comments. 2022-11-18 10:25:29 +01:00
dd709b0636 Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-11-18 10:16:40 +01:00
c41aae4423 Remove TIMEIT 2022-11-18 10:16:05 +01:00
596cb0ea31 Remove debug code. 2022-10-10 13:48:16 +02:00
5dd7744486 Fix compilation of VALIDATION checks. 2022-10-10 13:44:20 +02:00
7df5695ce7 Updated comments (c-style comments). 2022-10-10 13:28:45 +02:00
685f576a43 Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-10-10 12:45:22 +02:00
9232e2395a Added comment about data structure. 2022-09-27 11:34:07 +02:00
aee93f6071 Removed TODO. 2022-09-26 15:07:20 +02:00
f9d3cd5187 Moved versioncode to next subversion bump code-block. 2022-09-26 15:05:47 +02:00
fc816de31f Move BKE_uv_islands to pbvh namespace. 2022-09-26 14:59:20 +02:00
97c5230660 Fix crash when texturing on really dense mesh. 2022-09-26 14:38:36 +02:00
5d0529b3c0 Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-09-26 14:05:27 +02:00
5a83ff379f Increase seam-fix default value to 8. 2022-09-26 13:36:05 +02:00
19868fcdef Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-09-26 13:27:28 +02:00
a59725e718 Move implementation from header to cc. 2022-09-20 15:22:52 +02:00
bfc3f68f7f Hiding internal methods from API. 2022-09-20 14:34:53 +02:00
318009615d Updated license headers. 2022-09-20 14:01:55 +02:00
1c2342bc13 Renamed flip to flip_order. 2022-09-20 12:28:32 +02:00
69d70c7bbf Use BLI_edgehash_ensure_p to reduce lookups. 2022-09-20 11:33:06 +02:00
22bbf398cb Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-09-20 11:02:32 +02:00
52138ffef6 Removed Debug code. 2022-09-20 10:31:55 +02:00
fe53eeea79 Remove debug code. 2022-09-20 10:26:43 +02:00
ac42a5f792 Remove performance counter. 2022-09-20 09:48:36 +02:00
15920405fd DNA: Remove unnecessary studio light & light probe struct paddings 2022-09-20 09:48:36 +02:00
ee4565cdee GHOST/Wayland: correct flag for checking pressed keys
Check modifier keys using XKB_STATE_MODS_DEPRESSED which is used
to check if modifiers are physically held. In practice it's unlikely
this would have caused an error for key-maps in common use.
2022-09-20 09:48:36 +02:00
2bf872dfb5 Fix T101180: console HOME key doesn't work
Regression in [0], re-order the key-map so the home key
can be used for cursor motion.

[0]: 82fc52ffc8
2022-09-20 09:48:36 +02:00
7ee6d8fc12 Cleanup: use doxy sections 2022-09-20 09:48:36 +02:00
f2f0907003 Cleanup: spelling 2022-09-20 09:48:36 +02:00
2af06f8697 Cleanup: prefer 'arg' over 'params' for sphinx documentation
While both are supported, 'arg' is in more common use so prefer it.
2022-09-20 09:48:36 +02:00
Wannes Malfait
822fb550f2 Fix T101137: Crash with Transform Node
In `BKE_mesh_tag_coords_changed_uniformly` the checks for dirty vertex
and dirty poly normals were swapped around, causing an assert to be
triggered.

Differential Revision: https://developer.blender.org/D16002
2022-09-20 09:48:36 +02:00
bb7e8f4d4e Cleanup: Fix grammar in IndexRange header
Also make it more clear by avoiding repeating the name of the function.
2022-09-20 09:48:36 +02:00
1d711f5da6 Curves: Remove CurveEval and old Spline types
`CurveEval` was added for the first iteration of geometry nodes curve
support. Since then, it has been replaced by the new `Curves` type
which is designed to be much faster for many curves and better
integrated with the rest of Blender. Now that all curve nodes have
been moved to use `Curves` (T95443), the type can be removed,
along with the corresponding geometry component.
2022-09-20 09:48:36 +02:00
ca1b4be6f8 Curves: Port Curve to Points node to the new data-block
This is the last node to use the `CurveEval` type. Since the curve to
points node is basically the same as the resample node, now it just
reuses the resample code and moves the curve point `CustomData` to a
new point cloud at the end. I had to add support for sampling tangents
and normals to the resampling.

There is one behavior change: If the radius attribute doesn't exist,
the node won't set the radius to 1 for the output point cloud anymore.
Instead, the default radius for point clouds will be used.
That issue was similar to T99814.

Differential Revision: https://developer.blender.org/D16008
2022-09-20 09:48:36 +02:00
798b580811 Fix T101166: crash when creating group input socket
The issue was that not all Group Input nodes were updated when
the node group interface changed.
2022-09-20 09:48:36 +02:00
4932d833f1 Fix: Prevent clipping of node drop shadow
Fix clipping artifacts of node drop shadows that could occur
on hidden nodes, when using higher UI scaling.

Reviewed By: Hans Goudey

Differential Revision: http://developer.blender.org/D16007
2022-09-20 09:48:36 +02:00
d5e65790ec Fix: Make node position consistent when added through link drag search
The node position is specified in the coordinate space of the node
editor. The cursor position has to be divided by `UI_DPI_FAC` since it's
in view space but the offset is independent of any ui scaling.

Reviewed By: Hans Goudey

Differential Revision: http://developer.blender.org/D16006
2022-09-20 09:48:36 +02:00
2ce2e84b3b Cycles: sync changes from standalone repository
* Windows build fixes
* Workaround for Hydra + OpenColorIO link issue
* Bump version
2022-09-20 09:48:36 +02:00
4ac4d0b796 Build: fix gtest build flags affecting actual library
Switch to target_ functions to avoid this.
2022-09-20 09:48:36 +02:00
d5d9a3a885 Build: limit Py_ENABLE_SHARED to modules using Python headers
And remove Python flags from nodes, no longer needed.
2022-09-20 09:48:36 +02:00
9a5f2f4878 Build: disable gtests entirely for Python module
To avoid test failure on Windows.
2022-09-20 09:48:36 +02:00
Wannes Malfait
bef948d241 Geometry Nodes: New Face Set Boundaries node
With the recent addition of the UV unwrapping node, there is a need to
be able to create seams easily. This node does that by outputting a
selection of the boundaries between different input face sets. In the
context of UV mapping, one inputs the "patches" you want, and the node
gives you the seams needed to make those patches.

Differential Revision: https://developer.blender.org/D15423
2022-09-20 09:48:36 +02:00
Mattias Fredriksson
1941f039ce BLI: Add generic utlity for gathering values with indices
Add new functions to `array_utils` namespace called `gather(..)`.
Versions of `GVArray::materialize_compressed_to_uninitialized(..)` with
threading have been reimplemented locally in multiple geometry node
contexts. The purpose of this patch is therefore to:
 * Assemble these implementations in a single file.
 * Provide a naming convention that is easier to recognize.

Differential Revision: https://developer.blender.org/D15786
2022-09-20 09:48:36 +02:00
Mattias Fredriksson
24cab303f3 Curves: Correct and improve Catmull Rom interpolation
Correct interpolation of integer POD types for Catmull Rom
interpolation as implemented in eaf416693d.

**Problem description**
`attribute_math::DefaultMixer<T>::mix_in()` assumes/asserts positive
weights but the basis function for Catmull-Rom splines generates
negative weights (see image in revision). Passing negative weights will
yield correct result as sum(weights) = 1 (after multiplication by 0.5)
but the assert is still triggered in debug builds. This patch adjusts
the behavior by extending the mix functions with mix4(). The benefit
of using mix#() over a DefaultMixer is that the result no longer needs
to be divided by the weight sum, instead utilizing that the basis weight
sum is constant (see plot).

**Changes**
 * Added mix4() and updated catmull_rom::interpolate() to use it.
 * Removed TODOs from catmull_rom functions.
 * Moved mix definitions to be ordered as 2, 3, 4 in the header.

**Implementation specifics**
`catmull_rom::interpolate()` uses a constexpr to differentiate between
POD types which multiplies the result with 0.5 after weighting the
values, this reduces the number of multiplications for 1D, 2D, 3D
vectors (https://godbolt.org/z/8M1z9Pxx6). While this could be
considered unnecessary, I didn't want to change the original behavior
as it could influence performance (did not measure performance here
as this should ensure the logic is ~identical for FP types).

Differential Revision: https://developer.blender.org/D15997
2022-09-20 09:48:36 +02:00
418a0b21b5 Curves: Don't allow resolutions less than 1
While this worked, the result for curves with a resolution of zero was
just a single evaluated point, which isn't useful or intuitive. Using
the attribute validation from 8934f00ac5, make sure users
can't set values 0 or less.
2022-09-20 09:48:36 +02:00
78cdd02ab5 LineArt: Force intersection option.
This option allows easier setup of intersection overrides on more
complex scene structures. Setting force intersection would allow objects
to always produce intersection lines even against no-intersection ones.

Reviewed By: Aleš Jelovčan (frogstomp) Antonio Vazquez (antoniov)

Differential Revision: https://developer.blender.org/D15978
2022-09-20 09:48:36 +02:00
2b42cef78b Fix: Crash after recent attributes commit
Fixes test failures from 8934f00ac5.
2022-09-20 09:48:36 +02:00
02e96078c1 Fix OS-key events repeating on GHOST/Win32
Holding the OS (Windows) key on Win32 used key-repeat behavior.
While as far as I know it didn't cause user visible errors - sending
repeated modifier events isn't expected behavior and doesn't happen
on other platforms (or for other modifier keys).
2022-09-20 09:48:36 +02:00
48bcd3de8f GHOST: support left/right OS-key
Handling the OS key now match other modifiers in GHOST which detect
each key separately, making the behavior simpler to reason about since
mapping a single key to a modifier state is simpler, avoiding handling
that only applied to the OS-Key.

This means simulating key up/down events can use the correct modifier.

In the window-manager this is still only accessed accessed via KM_OSKEY.
2022-09-20 09:48:36 +02:00
9c6c4cc530 Attributes: Validate some builtin attributes for untrusted inputs
We expect some builtin attributes to have positive values or values
within a certain range, but currently there some cases where users
can set attributes to arbitrary values: the store named attribute node,
and the output attributes of the geometry nodes modifier. The set
material index node also needs validation.

This patch adds an `AttributeValidator` to the attribute API, which
can be used to correct values from these untrusted inputs if necessary.
As an alternative to D15548, this approach makes it much easier to
understand when validation is being applied, without the need to add
arguments to every attribute API method or complicate the virtual
array system.

Currently validation is provided with a multi-function. That integrates
well with the field evaluations that set these values now, but it could
be wrapped to be friendlier to other areas of Blender in the future.

The Python API is not handled here either. Currently I would prefer to
wait until we can integrate the C++ and C attribute APIs better before
addressing that.

Fixes T100952

Differential Revision: https://developer.blender.org/D15990
2022-09-20 09:48:36 +02:00
7f418290ba Mikktspace: Fix triangle reordering predicate
This only affected meshes containing degenerate triangles.
2022-09-20 09:48:36 +02:00
bd811e92c1 Fix: lite build on windows
writefile.cc includes BLI_winstuff.h which
includes Windows.h which supplies definitions
of min/max that conflict with the c++ headers

previously windows.h was only included when TBB was
enabled, the inclusion of BLI_winstuff.h now
makes this define mandatory for all configurations
2022-09-20 09:48:36 +02:00
b2dc1f9347 WM: send a modifier press when activating a window with modifier held
Previously the a simulated event was sent for releasing modifiers
on activation but pressing only set the eventstate flag.

Prefer the simulated events since press/release events are used in some
modal key-maps.
2022-09-20 09:48:36 +02:00
2ce295ebee WM: refactor modifier hold/release logic when activating a window
Initial support for matching left/right modifier keys for simulated
events - no functional changes.
2022-09-20 09:48:35 +02:00
c781a33209 PyDoc: correct parameter doc-strings & exception message 2022-09-20 09:48:35 +02:00
91a29361a9 Fix T100330: Remove Render Slot not working for first slot
This bug was caused by the weird ownership logic for render results.
Basically, the most recent render result is owned by the Render, while
all others are owned by the RenderSlots.
When a new render is started, the previous Render is handed over to its
slot, and the new slot is cleared. So far, so good.

However, when a slot is removed and happens to be the one with the most
recent render, this causes a complication.
The code handles this by making another slot the most recent one, along
with moving its result back to the Render, as if that had always been
the most recent one.

That works, unless there is no most recent render because you haven't
rendered anything yet. Unfortunately, there is no way to store "there
hasn't been a render yet", so the code still tries to perform this
handover but can't.
Previously, the code handled that case by just refusing to delete the
slot. However, this blocks users from deleting this slot.

But of course, if there hasn't been a render yet, the slots will not
contain anything yet, so this entire maneuver is pointless.
Therefore, the fix for the bug is to just skip it altogether if there
is no Render instead of failing the operation.

Technically, there is a weird corner case remaining, because Renders
are per-scene. Therefore, if a user renders images in one scene,
switches to a different scene, deletes a slot there and then switches
back, in some situations the result in the deleted slot might end up
in the next slot.
Unfortunately this is just a limitation of the weird split ownership
logic and can't just be worked around. The proper fix for this
probably would be to hand over ownership of the result from the Render
to the RenderSlot once the render is done, but this is quite complex.

Also fixes a crash when iuser->scene is NULL.
2022-09-20 09:48:35 +02:00
a59423378c GPencil: Remove Leak Size
This value was used to close gaps, but now with the new system is not needed.

Internally, still we need to keep a small leak size, but after doing a lot of test a 
value of 3 is perfect, so it's harcoded.
2022-09-20 09:48:35 +02:00
0f26fe63e8 GPencil: Move Gap Closure option to separated subpanel
Also removed leak size
2022-09-20 09:48:35 +02:00
9fa3234b03 GPencil: Change prop text to Closure Mode 2022-09-20 09:48:35 +02:00
3b9464dc6b GPencil: Hide the help Circles for gaps when gap is closed
To avoid too much noise, the help circles are only visible if the
the gap is still open. When the gap is closed, the circles are hidden.

Hiding the circles makes it easier to focus on what is problematic.
instead, to see many circles that are already resolved.
2022-09-20 09:48:35 +02:00
3446cf9ab6 GPencil: Rename Fill closure methods
The new names are:

* Radius
* Extend

The mode Radius + Extend has been removed.

Also, some code cleanup and format.
2022-09-20 09:48:35 +02:00
Dave Pagurek
a9a444510c GPencil: Add more types of stroke extensions when filling
The motivation for this change: while working on an animation recently, I found that there are some gaps that won't close easily via stroke extension or leak size checking. In D14698, I attempted to address this by changing the algorithm of the raster-space flood fill. This patch attempts to address the same issue in vector space by adding two new cases where stroke extensions are added, as suggested by @frogstomp:

  # **Points of high curvature:** when the curvature at a point is high enough that it's hard to visually distinguish between it and an endpoint, add a stroke extension out along the normal (pointing in the opposite direction of the stroke's acceleration.) This addresses cases where technically the endpoint points up, but there's a sharp corner right below it that should extend to connect.

  # **Stroke endpoints within a radius**: when two endpoints are close together, regardless of the angle they make, connect them if they are within a radius. This addresses cases like where the two endpoints have effectively parallel tangents, so extensions won't close the gap.

Reviewed By: antoniov, mendio, frogstomp

Differential Revision: https://developer.blender.org/D14809
2022-09-20 09:48:35 +02:00
cad85aab82 IndexRange: Add new intersect method
Returns a new range, that contains the intersection of the current one
with the given range.

This is helpful to select a portion of a range without having to deal with
all the asserts of other functions. The resulting range being always a
valid subrange, it can be used to iterate or copy a part of a vector.
2022-09-20 09:48:35 +02:00
5ac077b8f2 DRW: Debug: Fix row / column counters not being reset on init
This fixes the issues with CPU debug print not being in the right order.
2022-09-20 09:48:35 +02:00
724fa97008 DRW: GPU wrapper: Add new StorageVectorBuffer
Same as `StorageArrayBuffer` but has a length counter and act like a
`blender::Vector` you can clear and append to.
2022-09-20 09:48:35 +02:00
845505868b Geometry Nodes: use stringref instead of string in logger
This reduces logging overhead. The performance difference is only
significant when there are many fast nodes. In my test file with many
math nodes, the performance improved from 720ms to 630ms.
2022-09-20 09:48:35 +02:00
2d01bb692c GL: Framebuffer: Add support for empty framebuffer (no attachments)
This allows to reduce the memory footprint of very large framebuffers if
there is no need for any attachment.
2022-09-20 09:48:35 +02:00
42fc0fbff7 Cleanup: format 2022-09-20 09:48:35 +02:00
d901c2bcb6 Cleanup: compiler warnings 2022-09-20 09:48:35 +02:00
81eab10983 Cleanup: spelling, punctuation & repeated words in comments 2022-09-20 09:48:35 +02:00
e1e980b1c9 Cleanup: Use dedicated function to offset VSE strip handles 2022-09-20 09:48:35 +02:00
a4c39115a0 Fix T101098: Moving meta strip can change its length
Caused by clamping handle translation to strip bounds in functions
`SEQ_time_*_handle_frame_set()` to prevent strip ending in invalid
state. Issue happens when meta strip is moved so quickly, such that
immediate offset is greater than strip length.

Currently meta strip bounds are updated when any contained strip changes
its position, but this update always preserves meta strip position.
Transforming meta strip is not possible directly and all contained
strips are moved instead. Therefore this is 2-step process and fix needs
to be applied on update function and on translation function.

Inline offset handling without clamping in function
`SEQ_time_update_meta_strip_range()`.
Add new function `seq_time_translate_handles()` to move both handles at
once in `SEQ_transform_translate_sequence()`.
2022-09-20 09:48:35 +02:00
907c289ec0 Sculpt: add .sculpt to allow_procedural_attribute_access
Also cleaned up a comment.
2022-09-20 09:48:35 +02:00
72596b3d63 Sculpt: Improve performance when initializing face sets
Avoid conversion to `BMesh` for basic topology operations and data access.
Instead use a mesh map to retrieve the faces connected to each edge.
I observed performance improvements of 5x (600ms to 100ms) to 10x
(15s to 1s), with bigger changes for large meshes with more data layers
Switching to `std::queue` over Blender's `GSQueue` gave another
25% improvement.

Differential Revision: https://developer.blender.org/D15988
2022-09-20 09:48:35 +02:00
94500c8a13 Fix: Missing updates for multires sculpting
Caused by ee23f0f3fb, which removed the update tag when entering
sculpt mode, and by b5f7af31d6, which made these layers lazily
created, so they weren't always available at the start of a stroke. Now
update the evaluated mesh/multires CCG as necessary. Some updates
could potentially avoided when switching modes in the future, but for
now do it all the time.

Fixes T101116
Also fixes a crash when painting multires mask for the first time
2022-09-20 09:48:35 +02:00
9997f0eff6 Sculpt: New attribute API
New unified attribute API for sculpt code.

= Basic Design =

The sculpt attribute API can create temporary or permanent attributes (only supported in `PBVH_FACES` mode).  Attributes are created via `BKE_sculpt_attribute_ensure.`

Attributes can be explicit CustomData attributes or simple array-based pseudo-attributes (this is useful for PBVH_GRIDS and PBVH_BMESH).

== `SculptAttributePointers` ==

There is a structure in `SculptSession` for convenience attribute pointers, `ss->attrs`.  Standard attributes should assign these; the attribute API will automatically clear them when the associated attributes are released.  For example, the automasking code stores its factor attribute layer in `ss->attrs.automasking_factor`.

== Naming ==

Temporary attributes should use the SCULPT_ATTRIBUTE_NAME macro for naming, it takes an entry in `SculptAttributePointers` and builds a layer name.

== `SculptAttribute` ==

Attributes are referenced by a special `SculptAttribute` structure, which holds
all the info needed to look up elements of an attribute at run time.

All of these structures live in a preallocated flat array in `SculptSession`, `ss->temp_attributes`.  This is extremely important.  Since any change to the `CustomData` layout can in principle invalidate every extant `SculptAttribute`, having them all in one block of memory whose location doesn't change allows us to update them transparently.

This makes for much simpler code and eliminates bugs.  To see why this is tricky to get right, imagine we want to create three attributes in PBVH_BMESH mode and we provide our own `SculptAttribute` structs for the API to fill in.  Each new layer will invalidate the `CustomData` block offsets in the prior one, leading to memory corruption.

Reviewed by: Brecht Van Lommel
Differential Revision: https://developer.blender.org/D15496
Ref D15496
2022-09-20 09:48:35 +02:00
4c143f86bb Fix: Geometry nodes crash with undefined node
The new evaluator crashes for multi-input sockets coming from undefined
nodes. The multi-input socket lazy node tries to retrieve the default
value since the undefined node never created output values. But there
is no default value stored because the socket is linked.

Differential Revision: https://developer.blender.org/D15980
2022-09-20 09:48:35 +02:00
d301402fc6 Cleanup: Remove unused member variable in lazy function graph 2022-09-20 09:48:35 +02:00
183963d855 Fix T101100: missing smooth shading with linked subdivision surface in editmode
Smooth flag should come from the evaluated mesh, only selection and hidding
state should be mapped to the original bmesh.

Pre-existing issue revealed by refactor in b247588dc0.
2022-09-20 09:48:35 +02:00
5133abce4c Fix: crash when evaluating geometry nodes after deleting an unlinked node
This was essentially a use-after-free issue. When a geometry nodes
group changes it has to be preprocessed again before it can be evaluated.
This part was working, the issue was that parent node groups have to be
preprocessed as well, which was missing. The lazy-function graph cached
on the parent node group was still referencing data that was freed when
the child group changed.

Now the depsgraph makes sure that all relevant geometry node groups are
preprocessed again after a change.

This issue was found by Simon Thommes.
2022-09-20 09:48:35 +02:00
93b765f052 Fix Linux bpy wheel failing to install due to wrong ABI flags 2022-09-20 09:48:35 +02:00
0094444686 Sculpt: Move sculpt_face_set.c to C++ 2022-09-20 09:48:35 +02:00
f9d8f705d7 Removed debug message 2022-09-16 15:55:30 +02:00
b11ff34a6a Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-09-16 15:21:17 +02:00
2aa404885e Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-09-16 15:03:03 +02:00
b189346f4b Revert "EEVEE: Fix volumetric resolve in large scenes."
This reverts commit 34051fcc12.
Although for normal use this doesn't make a difference. But when working with
huge scenes and volumetrics + NVIDIA it made a work-around not possible anymore.

For the heist production we added a fix in the render-farm (enable GPU workarounds).
This {rB34051fcc12f388375697dcfc6da53e9909058fe1} made another work-around not
accessible anymore and it and was requested to revert this change.
2022-09-16 14:48:36 +02:00
c4471deea5 Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-09-16 09:41:22 +02:00
6c9d25e0e2 Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-09-14 15:47:14 +02:00
4496fb3f40 Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-09-09 10:48:08 +02:00
093f40e0fa Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-09-06 09:28:23 +02:00
428b1134b5 Use smaller resolution for masks. 2022-09-05 13:04:48 +02:00
7a88d7c505 Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-09-05 09:37:14 +02:00
4d503a0016 Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-08-03 12:04:22 +02:00
89d2732666 Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-08-03 10:06:49 +02:00
65ba7cc896 Remove TODO. 2022-08-03 09:20:24 +02:00
fd025e3c0d Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-08-03 09:13:29 +02:00
1d159e1882 Use a minimum extension length to reduce artifacts in dense geometry. 2022-07-15 16:34:34 +02:00
7ee7405623 Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-07-15 13:15:45 +02:00
d0c4c5924e Some cleanups. hiding internal API behind static functions. 2022-07-13 15:12:48 +02:00
1b966b4f8e Fix SVG_DEBUG. 2022-07-12 11:01:06 +02:00
d9fe355207 Move extendability flags from border edge to UVVertex. 2022-07-12 10:19:48 +02:00
aec5a965cf Remove resolved todo (UDIM) 2022-07-12 09:18:05 +02:00
f3a1fb9eef Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-07-12 08:49:50 +02:00
1e34ee0a7e Fixed SVG_DEBUG. 2022-07-11 15:36:40 +02:00
43aef4e69b Code cleanup: remove debug loop. 2022-07-11 15:36:40 +02:00
1d79b1d625 GPU: Update shader builder stubs.
Fixes workflow when using WITH_GPU_SHADER_BUILDER=On.
2022-07-11 15:36:40 +02:00
f8df5d681f Fix/Cleanup UI messages. 2022-07-11 15:36:40 +02:00
5305ed9d89 Deps Builder: Disable TermInfo and ncurses for DPC++
They are not strictly needed for compilation and disabling them makes
the compiler more portable without any special trickery.

This change aimed to solve problem which currently happens on the API
documentation build which does not have terminfo installed, but needs
to compile Cycles.

Note that the DPC++ is to be re-compiled.
2022-07-11 15:36:40 +02:00
e83064b2fc Cleanup: Remove unused operator name storage in UI lists 2022-07-11 15:36:40 +02:00
6b12116036 Fix T99383: Wrong origdata type in color filter 2022-07-11 15:36:40 +02:00
aa04d2330a Fix T94633: Sculpt mode missing check for hidden active object
Note there is a bug in BKE_object_is_visible_in_viewport, it
returns false when the object is in local mode.

The transform operator poll should do a similar test.  That
would allow us to move the test from sculpt_brush_strok_invoke
to SCULPT_mode_poll (at the moment we cannot do this due to
the brush operator falling through to the translate keymap
item in global view3d keymap).
2022-07-11 15:36:40 +02:00
1fba4043c2 PyAPI: add Matrix.is_identity read-only attribute
Add a convenient way of checking if the matrix is an identity matrix.
2022-07-11 15:36:40 +02:00
ad148e4fda GPencil: Dot-dash modifier rename segment bug fix.
This patch fixes naming and renaming issue with dot-dash modifier segment list.

Before: when double clicking and exiting it would append
number at the end regardless of name being changed or not.

Now it works like in other areas.

Authored by: Aleš Jelovčan (frogstomp)

Reviewed By: YimingWu (NicksBest)

Differential Revision: https://developer.blender.org/D15359
2022-07-11 15:36:40 +02:00
3e3fd6cacc Cleanup: spelling in comments 2022-07-11 15:36:40 +02:00
439a1d240e Cleanup: remove unused GHOST function getAnyModifiedState.
Remove unused GHOST_WindowManager::getAnyModifiedState()
2022-07-11 15:36:40 +02:00
bb34517342 Revert "Fix an assert trip in boolean tickled by D11272 example."
This reverts commit 6543290116.
It broke tests and I don't know why, so reverting this while
figuring that out.
2022-07-11 15:36:40 +02:00
28754e9c53 Fix an assert trip in boolean tickled by D11272 example.
The face merging code in exact boolean made an assumption that
the tesselated original face was manifold except at the boundaries.
This should be true but sometimes (e.g., if the input faces have
self-intersection, as happens in the example), it is not.
This commit makes face merging tolerant of such a situation.
It might leave some stray edges from triangulation, but it should
only happen if the input is malformed.
Note: the input may be malformed if there were previous booleans
in the stack, since snapping the exact result to float coordinates
is not guaranteed to leave the mesh without defects.
2022-07-11 15:36:40 +02:00
3db63e67d4 Fix T99532: New OBJ importer in some cases fails to import faces
The importer code was written under incorrect assumption that vertex
data (v, vn, vt commands etc.) are grouped by object, i.e. follow the
o command, and that each object has its own vertex data commands. This
is not the case -- all the vertex data in the whole OBJ file is
"global", with no relation to any objects/groups; it's just that the
faces belong to the object, and then they pull in any vertices they
like.

This patch fixes this incorrect assumption in the importer:

- Vertex data is now properly global; no need to track some sort of
  "offsets" per object like it was doing before.
- For each object, face definitions track the minimum & maximum vertex
  indices referenced by the object, and then all that vertex range is
  created in the final Blender object. Note: it might be (unusual, but
  possible) that an object does not reference a sequential range of
  vertices, e.g. just a single face with vertex indices 1, 10, 100 --
  the resulting Blender mesh will have all the 100 vertices (some
  "loose" without belonging to a face). It should be possible to track
  the used vertices exactly (e.g. with a vector set), but I haven't
  done that for performance reasons.

Reviewed By: Howard Trickey
Differential Revision: https://developer.blender.org/D15410
2022-07-11 15:36:40 +02:00
1275786238 Fix T99536: new 3.2 OBJ importer fails with trailing space after wrapped lines
Address the issue by re-working line continuation handling: stop
trying to parse sequences like "backslash, newline" (which is the
bug: it should also handle "backslash, possible whitespace, newline")
during parsing. Instead, fixup line continuations after reading chunks
of input file data - turn backslash and the following newline into
spaces. The rest of parsing code does not have to be aware of them
at all then.

Makes the file attached to T99536 load correctly now. Also will extend
one of the test files in subversion tests repo to contain backslashes
followed by newlines.
2022-07-11 15:36:40 +02:00
4e4e9a02a9 Fix cursor display size with tablet input in GHOST/Wayland
The scale for tablet cursor surfaces was never set, making them display
larger. Now the outputs scale is set for mouse & tablet cursors.
2022-07-11 15:36:40 +02:00
51ea26f1d9 Cleanup: split out wl_buffer creation into a utility function
Simplify logic for initializing the wl_buffer, ensure the cursors
custom data is never heft in a half initialized state.
Also remove the need for multiple calls to close when handling errors.
2022-07-11 15:36:40 +02:00
833b9b7749 Cleanup: remove buffer_t in GHOST/Wayland
This was allocated and only used to store the custom cursor data.
Use a pointer & size member instead for simplicity.
2022-07-11 15:36:40 +02:00
a7a56cab3b Cleanup: split memfd_create into it's own function for Wayland
Avoid ifdef's in cursor loading by creating a memfd_create_sealed
utility function that works irrespective of memfd_create availability.
2022-07-11 15:36:40 +02:00
ae4dc20254 Fix resource leaks setting custom cursors in Wayland
- Memory from the prior cursor was never un-mapped.
- posix_fallocate failure left a file handle open..
2022-07-11 15:36:40 +02:00
0c5684283c UI: renaming fIle browser thumbnail sizes
Rename the thumbnail size from Regular to Medium since it's the typical
way to refer to sizing in American English

Reviewed By: Campbell Barton
Differential Revision: https://developer.blender.org/D15305
2022-07-11 15:36:40 +02:00
d71fa2429f Weight & Vertex Paint: always respect edit mode hiding on faces.
In some cases it is mandatory to be able to hide parts of the mesh
in order to paint certain areas. The Mask modifier doesn't work in
weight paint, and edit mode hiding requires using selection, which
is not always convenient.

This makes the weight and vertex paint modes always respect edit mode
hiding like sculpt mode. The change in behavior affects drawing and
building paint PBVH. Thus it affects brushes, but not menu operators
like Smooth or Normalize.

In addition, this makes the Alt-H shortcut available even without
any selection enabled, and implements Hide for vertex selection.

Differential Revision: https://developer.blender.org/D14163
2022-07-11 15:36:40 +02:00
1566f246f9 Cleanup: quiet class-memaccess warning 2022-07-11 15:36:40 +02:00
3dd1187c45 Cleanup: Remove unused variable 2022-07-11 15:36:40 +02:00
fa00bfaf2a Fix T99494: Transition effects not working correctly
This was caused by strip content length and start position being
incorrect. Previously this was set from strip boundary by update
function, but it was removed.

Add back code to set effect strip start and length.

Previously content length was always 1 for effects, but now it must
correspond to strip length. Because of this workaround for speed effect
to get this apparent content length was removed.
2022-07-11 15:36:40 +02:00
cd04c61f4f Fix bug in recently added MutableVArraySpan move constructor 2022-07-11 15:36:40 +02:00
4ed11b43f9 Curves: use consistent default radius for Cycles, Eevee, Set Curve Radius node
To avoid Cycles not showing any hair by default, and to avoid very slow render
due to many overlaps with the previous 1 meter default in the node.

Fixes T97584, T99319

Differential Revision: https://developer.blender.org/D15405
2022-07-11 15:36:40 +02:00
a5d5423827 Cleanup: convert brush.c to C++
In preparation of refactoring for texture nodes.
2022-07-11 15:36:40 +02:00
72501b5448 Geometry Nodes: new geometry attribute API
Currently, there are two attribute API. The first, defined in `BKE_attribute.h` is
accessible from RNA and C code. The second is implemented with `GeometryComponent`
and is only accessible in C++ code. The second is widely used, but only being
accessible through the `GeometrySet` API makes it awkward to use, and even impossible
for types that don't correspond directly to a geometry component like `CurvesGeometry`.

This patch adds a new attribute API, designed to replace the `GeometryComponent`
attribute API now, and to eventually replace or be the basis of the other one.

The basic idea is that there is an `AttributeAccessor` class that allows code to
interact with a set of attributes owned by some geometry. The accessor itself has
no ownership. `AttributeAccessor` is a simple type that can be passed around by
value. That makes it easy to return it from functions and to store it in containers.

For const-correctness, there is also a `MutableAttributeAccessor` that allows
changing individual and can add or remove attributes.

Currently, `AttributeAccessor` is composed of two pointers. The first is a pointer
to the owner of the attribute data. The second is a pointer to a struct with
function pointers, that is similar to a virtual function table. The functions
know how to access attributes on the owner.

The actual attribute access for geometries is still implemented with the `AttributeProvider`
pattern, which makes it easy to support different sources of attributes on a
geometry and simplifies dealing with built-in attributes.

There are different ways to get an attribute accessor for a geometry:
* `GeometryComponent.attributes()`
* `CurvesGeometry.attributes()`
* `bke::mesh_attributes(const Mesh &)`
* `bke::pointcloud_attributes(const PointCloud &)`

All of these also have a `_for_write` variant that returns a `MutabelAttributeAccessor`.

Differential Revision: https://developer.blender.org/D15280
2022-07-11 15:36:39 +02:00
9576b5ba32 Linux: Move Mesa software OpenGL libraries to sub-directory
Allows to put libraries which are always needed by Blender into the
lib/ folder and not worry about OpenGL libraries picked up from there.

Currently no functional changes as we do not yet have dynamic libraries
which we load at startup. It allows to use direct linking of oneAPI
Cycles device (see D15397), also it is something which would need to
happen to support USD/Hydra/TBB compiler as dynamic libraries in the
future.

Differential Revision: https://developer.blender.org/D15403
2022-07-11 15:36:39 +02:00
e5591bbb4f Cycles: enable oneAPI in Linux release builds
with a very high min-driver version requirement, placeholder until JIT
CentOS runtime compilation issue gets fixed in a defined version.
min-driver version check can be worked around by setting
CYCLES_ONEAPI_ALL_DEVICES environment variable.
2022-07-11 15:36:39 +02:00
34f59b1d4c Fix T99191: Boolean modifier creates invalid material indices
Similar to 1a6d0ec71c which changed the mesh boolean node (and
also caused this bug), this commit changes the material mapping for the
exact mode of the boolean modifier. Now the result should contain any
material on the faces of the input objects (including materials linked
to objects and meshes). The improvement is possible because materials
can be changed during evaluation (as of 1a81d268a1).

Differential Revision: https://developer.blender.org/D15365
2022-07-11 15:36:39 +02:00
ffc386b92f Hair Curves: The new curves object is now available
This commit doesn't implement any new feature but makes the new curves
object type no longer experimental.

Documentation:

* https://docs.blender.org/manual/en/3.3/modeling/curves/primitives.html#empty-hair
* https://docs.blender.org/manual/en/3.3/sculpt_paint/curves_sculpting/introduction.html

Note: This also makes the Selection Paint tool available. This tool
should have been moved out of the "New Curves Tool" flag when we got the
selection drawing to work.

Differential Revision: https://developer.blender.org/D15402
2022-07-11 15:36:39 +02:00
09e1175aa1 Cleanup: make format 2022-07-11 15:36:39 +02:00
77bb17b661 Curves: support deforming curves on surface
Curves that are attached to a surface can now follow the surface when
it is modified using shape keys or modifiers (but not when the original
surface is deformed in edit or sculpt mode).

The surface is allowed to be changed in any way that keeps uv maps
intact. So deformation is allowed, but also some topology changes like
subdivision.

The following features are added:
* A new `Deform Curves on Surface` node, which deforms curves with
  attachment information based on the surface object and uv map set
  in the properties panel.
* A new `Add Rest Position` checkbox in the shape keys panel. When checked,
  a new `rest_position` vector attribute is added to the mesh before shape
  keys and modifiers are applied. This is necessary to support proper
  deformation of the curves, but can also be used for other purposes.
* The `Add > Curve > Empty Hair` operator now sets up a simple geometry
  nodes setup that deforms the hair. It also makes sure that the rest
  position attribute is added to the surface.
* A new `Object (Attach Curves to Surface)` operator in the `Set Parent To`
  (ctrl+P) menu, which attaches existing curves to the surface and sets the
  surface object as parent.

Limitations:
* Sculpting the procedurally deformed curves will be implemented separately.
* The `Deform Curves on Surface` node is not generic and can only be used
  for one specific purpose currently. We plan to generalize this more in the
  future by adding support by exposing more inputs and/or by turning it into
  a node group.

Differential Revision: https://developer.blender.org/D14864
2022-07-11 15:36:39 +02:00
253601d2ea Fix build error without unity build, after recent changes 2022-07-11 15:36:39 +02:00
7820729053 Support for seam fixing with UDIM tiles. 2022-07-08 15:21:29 +02:00
9980b2bea5 Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-07-08 14:08:07 +02:00
13c202a263 Use VectorList in UVIsland. 2022-07-07 11:03:17 +02:00
a042d365b3 Added VectorList from D13289. 2022-07-07 11:03:17 +02:00
1acb1b8cbd Added VectorList from D13289. 2022-07-07 10:32:39 +02:00
2016aabc51 Reserve space to handle larger meshes. 2022-07-07 10:26:43 +02:00
cd6270ff58 Removed some TODO's that were already fixed. 2022-07-07 09:02:57 +02:00
085b3afb1e Formatting. 2022-07-06 14:02:39 +02:00
88f98c0df4 Improve performance fan uv coordinates. 2022-07-06 13:26:39 +02:00
c195be846d Improve pixel extraction performance by removing nested for loops. 2022-07-06 12:11:03 +02:00
6be9b26bec Something was missing in previous commit. Half of the file was committed.... 2022-07-05 13:09:49 +02:00
ab2ace99a7 Use vertex lookup to lookup edges as well. 2022-07-05 13:08:29 +02:00
1e2f4fac00 Use KDTree for uvvertex lookup (currently slowing down due to rebalancing overhead. 2022-07-05 12:10:35 +02:00
ff72605516 Remove early exit as with the new extraction method it would always fail. 2022-07-05 09:17:27 +02:00
3faaacc50d Improve performance initial uv island extraction. 2022-07-05 08:58:18 +02:00
b7ff281fd1 Add eartly exit when looking for shared uv edges in islands. 2022-07-04 15:48:45 +02:00
81a5ed5ddb Added timeit sections. 2022-07-04 14:06:37 +02:00
1401125f7b Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-06-28 14:44:15 +02:00
0d743e8498 Removed unused code. 2022-06-20 12:44:04 +02:00
ae63b01016 Removed debug code as it slowed down the algorithm. 2022-06-20 08:33:59 +02:00
6a3c082fe9 Do not output vertex to debug svg. 2022-06-17 15:06:49 +02:00
b03d22e39d Limit upto 1 extension, due to mismatch somewhere.... 2022-06-17 14:21:16 +02:00
13b04dec48 renamed FanSegment => InnerEdge 2022-06-17 12:42:21 +02:00
d9c7e240ed Reduce pixel extraction based on the uv island dilated mask. 2022-06-17 11:27:45 +02:00
1b7e01fcd0 Fix incorrect border update when wrapping around the list of edges. 2022-06-15 17:15:50 +02:00
213145ba3a Fix extending when corner verts are different. 2022-06-15 13:47:45 +02:00
0132873a37 Flipping borders. 2022-06-15 08:49:18 +02:00
198bcbb4d5 Fix issue where input mesh wasn't compact. 2022-06-14 14:53:04 +02:00
ec703819c1 Add missing segments from a fan.
TODO: Borders should be updated.
2022-06-14 13:11:48 +02:00
fcc6557ebd Fill UVPrimitive from a MeshPrimitive. 2022-06-14 10:52:57 +02:00
aa355118cf Removed loops from the UVVertex as it became to confusing. 2022-06-14 08:21:27 +02:00
02f628fec6 Use functions to detect an error in the algorithm. Still looking for the error. 2022-06-13 15:35:24 +02:00
e97c6511fb Added more debug statements.
There seems to be some data mismatch that corrupts some UVPrimitives.
The corrupted UVPrimitives don't point to the same geometric primitives
it was generated from.
This doesn't happen in the first iteration, but when generating a derivative
from another derivative.
2022-06-10 15:34:03 +02:00
7c435f069a WIP adding missing segments into uv space. 2022-06-10 12:38:42 +02:00
8bbecabd3d Update borders. 2022-06-10 12:16:32 +02:00
79907fb0be Fix reading incorrect uv coordinates. 2022-06-10 11:09:20 +02:00
6121579f5c First stroke, no crash, but incorrect results. 2022-06-10 08:57:20 +02:00
f15f8d8f32 Merge branch 'master' into temp-T97352-3d-texturing-seam-bleeding-b2 2022-06-08 13:27:03 +02:00
9a64c8b8ef WIP. Starting changes in pbvh_pixels. 2022-06-08 13:05:48 +02:00
a8715cc5b0 use border length to determine length of new edge. 2022-06-07 14:59:27 +02:00
6a6e7cb506 Fix writing to temp variable. 2022-06-07 14:45:20 +02:00
8ef03b070d Use edges for borders. 2022-06-07 14:39:08 +02:00
d176c5789b Temp commit before removing UVBorderVert. 2022-06-07 11:37:29 +02:00
6f7c3f40c3 Let Bordervert point to uvvertex. 2022-06-07 09:25:32 +02:00
fad9241b9f Make UVIsland own UVVertex. 2022-06-07 09:07:45 +02:00
9465948902 Use ptr to understand better what is needed. 2022-06-03 15:54:17 +02:00
c38dae0fab Commit before continuing. 2022-06-01 12:29:26 +02:00
509252fa3d Calculating current UV coordinates. 2022-05-31 15:50:59 +02:00
6011bf549a Commit before restructurizing. 2022-05-31 14:01:41 +02:00
bdcb11abb7 Clean apply on master. 2022-05-25 15:15:03 +02:00
19 changed files with 2961 additions and 88 deletions

View File

@@ -91,6 +91,15 @@ typedef struct {
float (*color)[4];
} PBVHColorBufferNode;
typedef struct PBVHPixels {
/**
* Storage for texture painting on PBVH level.
*
* Contains #blender::bke::pbvh::pixels::PBVHData
*/
void *data;
} PBVHPixels;
typedef struct PBVHPixelsNode {
/**
* Contains triangle/pixel data used during texture painting.

View File

@@ -18,8 +18,46 @@
namespace blender::bke::pbvh::pixels {
struct TrianglePaintInput {
int3 vert_indices;
/**
* Data shared between pixels that belong to the same triangle.
*
* Data is stored as a list of structs, grouped by usage to improve performance (improves CPU
* cache prefetching).
*/
struct PaintGeometryPrimitives {
/** Data accessed by the inner loop of the painting brush. */
Vector<int3> vert_indices;
public:
void append(const int3 vert_indices)
{
this->vert_indices.append(vert_indices);
}
const int3 &get_vert_indices(const int index) const
{
return vert_indices[index];
}
void clear()
{
vert_indices.clear();
}
int64_t size() const
{
return vert_indices.size();
}
int64_t mem_size() const
{
return size() * sizeof(int3);
}
};
struct UVPrimitivePaintInput {
/** Corresponding index into PaintGeometryPrimitives */
int64_t geometry_primitive_index;
/**
* Delta barycentric coordinates between 2 neighboring UV's in the U direction.
*
@@ -33,34 +71,27 @@ struct TrianglePaintInput {
* delta_barycentric_coord_u is initialized in a later stage as it requires image tile
* dimensions.
*/
TrianglePaintInput(const int3 vert_indices)
: vert_indices(vert_indices), delta_barycentric_coord_u(0.0f, 0.0f)
UVPrimitivePaintInput(int64_t geometry_primitive_index)
: geometry_primitive_index(geometry_primitive_index), delta_barycentric_coord_u(0.0f, 0.0f)
{
}
};
/**
* Data shared between pixels that belong to the same triangle.
*
* Data is stored as a list of structs, grouped by usage to improve performance (improves CPU
* cache prefetching).
*/
struct Triangles {
struct PaintUVPrimitives {
/** Data accessed by the inner loop of the painting brush. */
Vector<TrianglePaintInput> paint_input;
Vector<UVPrimitivePaintInput> paint_input;
public:
void append(const int3 vert_indices)
void append(int64_t geometry_primitive_index)
{
this->paint_input.append(TrianglePaintInput(vert_indices));
this->paint_input.append(UVPrimitivePaintInput(geometry_primitive_index));
}
TrianglePaintInput &get_paint_input(const int index)
UVPrimitivePaintInput &last()
{
return paint_input[index];
return paint_input.last();
}
const TrianglePaintInput &get_paint_input(const int index) const
const UVPrimitivePaintInput &get_paint_input(uint64_t index) const
{
return paint_input[index];
}
@@ -77,7 +108,7 @@ struct Triangles {
int64_t mem_size() const
{
return paint_input.size() * sizeof(TrianglePaintInput);
return size() * sizeof(UVPrimitivePaintInput);
}
};
@@ -92,7 +123,7 @@ struct PackedPixelRow {
/** Number of sequential pixels encoded in this package. */
ushort num_pixels;
/** Reference to the pbvh triangle index. */
ushort triangle_index;
ushort uv_primitive_index;
};
/**
@@ -148,7 +179,7 @@ struct NodeData {
Vector<UDIMTilePixels> tiles;
Vector<UDIMTileUndo> undo_regions;
Triangles triangles;
PaintUVPrimitives uv_primitives;
NodeData()
{
@@ -201,7 +232,7 @@ struct NodeData {
void clear_data()
{
tiles.clear();
triangles.clear();
uv_primitives.clear();
}
static void free_func(void *instance)
@@ -211,7 +242,18 @@ struct NodeData {
}
};
struct PBVHData {
/* Per UVPRimitive contains the paint data. */
PaintGeometryPrimitives geom_primitives;
void clear_data()
{
geom_primitives.clear();
}
};
NodeData &BKE_pbvh_pixels_node_data_get(PBVHNode &node);
void BKE_pbvh_pixels_mark_image_dirty(PBVHNode &node, Image &image, ImageUser &image_user);
PBVHData &BKE_pbvh_pixels_data_get(PBVH &pbvh);
} // namespace blender::bke::pbvh::pixels

View File

@@ -0,0 +1,748 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <fstream>
#include <optional>
#include "BLI_array.hh"
#include "BLI_edgehash.h"
#include "BLI_float3x3.hh"
#include "BLI_map.hh"
#include "BLI_math.h"
#include "BLI_math_vec_types.hh"
#include "BLI_rect.h"
#include "BLI_vector.hh"
#include "BLI_vector_list.hh"
#include "DNA_meshdata_types.h"
namespace blender::bke::uv_islands {
struct MeshEdge;
struct MeshPrimitive;
struct UVBorder;
struct UVEdge;
struct UVIslands;
struct UVIslandsMask;
struct UVPrimitive;
struct UVPrimitiveEdge;
struct UVVertex;
struct MeshVertex {
int64_t v;
Vector<MeshEdge *> edges;
};
struct MeshUVVert {
MeshVertex *vertex;
float2 uv;
int64_t loop;
};
struct MeshEdge {
MeshVertex *vert1;
MeshVertex *vert2;
Vector<MeshPrimitive *> primitives;
};
/** Represents a triangle in 3d space (MLoopTri) */
struct MeshPrimitive {
int64_t index;
int64_t poly;
Vector<MeshEdge *, 3> edges;
Vector<MeshUVVert, 3> vertices;
/**
* UV island this primitive belongs to. This is used to speed up the initial uv island
* extraction, but should not be used when extending uv islands.
*/
int64_t uv_island_id;
MeshUVVert *get_other_uv_vertex(const MeshVertex *v1, const MeshVertex *v2)
{
BLI_assert(vertices[0].vertex == v1 || vertices[1].vertex == v1 || vertices[2].vertex == v1);
BLI_assert(vertices[0].vertex == v2 || vertices[1].vertex == v2 || vertices[2].vertex == v2);
for (MeshUVVert &uv_vertex : vertices) {
if (uv_vertex.vertex != v1 && uv_vertex.vertex != v2) {
return &uv_vertex;
}
}
return nullptr;
}
rctf uv_bounds() const;
bool has_shared_uv_edge(const MeshPrimitive *other) const
{
int shared_uv_verts = 0;
for (const MeshUVVert &vert : vertices) {
for (const MeshUVVert &other_vert : other->vertices) {
if (vert.uv == other_vert.uv) {
shared_uv_verts += 1;
}
}
}
return shared_uv_verts >= 2;
}
};
/**
* MeshData contains input geometry data converted in a list of primitives, edges and vertices for
* quick access for both local space and uv space.
*/
struct MeshData {
public:
const MLoopTri *looptri;
const int64_t looptri_len;
const int64_t vert_len;
const MLoop *mloop;
const MLoopUV *mloopuv;
public:
Vector<MeshPrimitive> primitives;
Vector<MeshEdge> edges;
Vector<MeshVertex> vertices;
/** Total number of uv islands detected. */
int64_t uv_island_len;
explicit MeshData(const MLoopTri *looptri,
const int64_t looptri_len,
const int64_t vert_len,
const MLoop *mloop,
const MLoopUV *mloopuv)
: looptri(looptri),
looptri_len(looptri_len),
vert_len(vert_len),
mloop(mloop),
mloopuv(mloopuv)
{
init_vertices();
init_primitives();
init_edges();
init_primitive_uv_island_ids();
}
void init_vertices()
{
vertices.reserve(vert_len);
for (int64_t i = 0; i < vert_len; i++) {
MeshVertex vert;
vert.v = i;
vertices.append(vert);
}
}
void init_primitives()
{
primitives.reserve(looptri_len);
for (int64_t i = 0; i < looptri_len; i++) {
const MLoopTri &tri = looptri[i];
MeshPrimitive primitive;
primitive.index = i;
primitive.poly = tri.poly;
for (int j = 0; j < 3; j++) {
MeshUVVert uv_vert;
uv_vert.loop = tri.tri[j];
uv_vert.vertex = &vertices[mloop[uv_vert.loop].v];
uv_vert.uv = mloopuv[uv_vert.loop].uv;
primitive.vertices.append(uv_vert);
}
primitives.append(primitive);
}
}
void init_edges()
{
edges.reserve(looptri_len * 2);
EdgeHash *eh = BLI_edgehash_new_ex(__func__, looptri_len * 3);
for (int64_t i = 0; i < looptri_len; i++) {
const MLoopTri &tri = looptri[i];
MeshPrimitive &primitive = primitives[i];
for (int j = 0; j < 3; j++) {
int v1 = mloop[tri.tri[j]].v;
int v2 = mloop[tri.tri[(j + 1) % 3]].v;
/* TODO: Use lookup_ptr to be able to store edge 0. */
void *v = BLI_edgehash_lookup(eh, v1, v2);
int64_t edge_index;
if (v == nullptr) {
edge_index = edges.size();
BLI_edgehash_insert(eh, v1, v2, POINTER_FROM_INT(edge_index + 1));
MeshEdge edge;
edge.vert1 = &vertices[v1];
edge.vert2 = &vertices[v2];
edges.append(edge);
MeshEdge *edge_ptr = &edges.last();
vertices[v1].edges.append(edge_ptr);
vertices[v2].edges.append(edge_ptr);
}
else {
edge_index = POINTER_AS_INT(v) - 1;
}
MeshEdge *edge = &edges[edge_index];
edge->primitives.append(&primitive);
primitive.edges.append(edge);
}
}
BLI_edgehash_free(eh, nullptr);
}
static const int64_t INVALID_UV_ISLAND_ID = -1;
/**
* NOTE: doesn't support weird topology where unconnected mesh primitives share the same uv
* island. For a accurate implementation we should use implement an uv_prim_lookup.
*/
static void extract_uv_neighbors(Vector<MeshPrimitive *> &prims_to_add, MeshPrimitive *primitive)
{
for (MeshEdge *edge : primitive->edges) {
for (MeshPrimitive *other_primitive : edge->primitives) {
if (primitive == other_primitive) {
continue;
}
if (other_primitive->uv_island_id != MeshData::INVALID_UV_ISLAND_ID) {
continue;
}
if (primitive->has_shared_uv_edge(other_primitive)) {
prims_to_add.append(other_primitive);
}
}
}
}
void init_primitive_uv_island_ids()
{
for (MeshPrimitive &primitive : primitives) {
primitive.uv_island_id = INVALID_UV_ISLAND_ID;
}
int64_t uv_island_id = 0;
Vector<MeshPrimitive *> prims_to_add;
for (MeshPrimitive &primitive : primitives) {
/* Early exit when uv island id is already extracted during uv neighbor extractions. */
if (primitive.uv_island_id != INVALID_UV_ISLAND_ID) {
continue;
}
prims_to_add.append(&primitive);
while (!prims_to_add.is_empty()) {
MeshPrimitive *primitive = prims_to_add.pop_last();
primitive->uv_island_id = uv_island_id;
extract_uv_neighbors(prims_to_add, primitive);
}
uv_island_id++;
}
uv_island_len = uv_island_id;
}
};
struct UVVertex {
MeshVertex *vertex;
/* Position in uv space. */
float2 uv;
/* uv edges that share this UVVertex. */
Vector<UVEdge *> uv_edges;
struct {
bool is_border : 1;
bool is_extended : 1;
} flags;
explicit UVVertex()
{
flags.is_border = false;
flags.is_extended = false;
}
explicit UVVertex(const MeshUVVert &vert) : vertex(vert.vertex), uv(vert.uv)
{
flags.is_border = false;
flags.is_extended = false;
}
};
struct UVEdge {
std::array<UVVertex *, 2> vertices;
Vector<UVPrimitive *, 2> uv_primitives;
bool has_shared_edge(const MeshUVVert &v1, const MeshUVVert &v2) const
{
return (vertices[0]->uv == v1.uv && vertices[1]->uv == v2.uv) ||
(vertices[0]->uv == v2.uv && vertices[1]->uv == v1.uv);
}
bool has_shared_edge(const UVVertex &v1, const UVVertex &v2) const
{
return (vertices[0]->uv == v1.uv && vertices[1]->uv == v2.uv) ||
(vertices[0]->uv == v2.uv && vertices[1]->uv == v1.uv);
}
bool has_shared_edge(const UVEdge &other) const
{
return has_shared_edge(*other.vertices[0], *other.vertices[1]);
}
bool has_same_vertices(const MeshVertex &vert1, const MeshVertex &vert2) const
{
return (vertices[0]->vertex == &vert1 && vertices[1]->vertex == &vert2) ||
(vertices[0]->vertex == &vert2 && vertices[1]->vertex == &vert1);
}
bool has_same_uv_vertices(const UVEdge &other) const
{
return has_shared_edge(other) &&
has_same_vertices(*other.vertices[0]->vertex, *other.vertices[1]->vertex);
;
}
bool has_same_vertices(const MeshEdge &edge) const
{
return has_same_vertices(*edge.vert1, *edge.vert2);
}
bool is_border_edge() const
{
return uv_primitives.size() == 1;
}
void append_to_uv_vertices()
{
for (UVVertex *vertex : vertices) {
vertex->uv_edges.append_non_duplicates(this);
}
}
UVVertex *get_other_uv_vertex(const MeshVertex *vertex)
{
if (vertices[0]->vertex == vertex) {
return vertices[1];
}
return vertices[0];
}
};
struct UVPrimitive {
/**
* Index of the primitive in the original mesh.
*/
MeshPrimitive *primitive;
Vector<UVEdge *, 3> edges;
explicit UVPrimitive(MeshPrimitive *primitive) : primitive(primitive)
{
}
void append_to_uv_edges()
{
for (UVEdge *uv_edge : edges) {
uv_edge->uv_primitives.append_non_duplicates(this);
}
}
void append_to_uv_vertices()
{
for (UVEdge *uv_edge : edges) {
uv_edge->append_to_uv_vertices();
}
}
Vector<std::pair<UVEdge *, UVEdge *>> shared_edges(UVPrimitive &other)
{
Vector<std::pair<UVEdge *, UVEdge *>> result;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (edges[i]->has_shared_edge(*other.edges[j])) {
result.append(std::pair<UVEdge *, UVEdge *>(edges[i], other.edges[j]));
}
}
}
return result;
}
bool has_shared_edge(const UVPrimitive &other) const
{
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (edges[i]->has_shared_edge(*other.edges[j])) {
return true;
}
}
}
return false;
}
bool has_shared_edge(const MeshPrimitive &primitive) const
{
for (const UVEdge *uv_edge : edges) {
const MeshUVVert *v1 = &primitive.vertices.last();
for (int i = 0; i < primitive.vertices.size(); i++) {
const MeshUVVert *v2 = &primitive.vertices[i];
if (uv_edge->has_shared_edge(*v1, *v2)) {
return true;
}
v1 = v2;
}
}
return false;
}
/**
* Get the UVVertex in the order that the verts are ordered in the MeshPrimitive.
*/
const UVVertex *get_uv_vertex(const uint8_t mesh_vert_index) const
{
const MeshVertex *mesh_vertex = primitive->vertices[mesh_vert_index].vertex;
for (const UVEdge *uv_edge : edges) {
for (const UVVertex *uv_vert : uv_edge->vertices) {
if (uv_vert->vertex == mesh_vertex) {
return uv_vert;
}
}
}
BLI_assert_unreachable();
return nullptr;
}
/**
* Get the UVEdge that share the given uv coordinates.
* Will assert when no UVEdge found.
*/
UVEdge *get_uv_edge(const float2 uv1, const float2 uv2) const
{
for (UVEdge *uv_edge : edges) {
const float2 &e1 = uv_edge->vertices[0]->uv;
const float2 &e2 = uv_edge->vertices[1]->uv;
if ((e1 == uv1 && e2 == uv2) || (e1 == uv2 && e2 == uv1)) {
return uv_edge;
}
}
BLI_assert_unreachable();
return nullptr;
}
UVEdge *get_uv_edge(const MeshVertex *v1, const MeshVertex *v2) const
{
for (UVEdge *uv_edge : edges) {
const MeshVertex *e1 = uv_edge->vertices[0]->vertex;
const MeshVertex *e2 = uv_edge->vertices[1]->vertex;
if ((e1 == v1 && e2 == v2) || (e1 == v2 && e2 == v1)) {
return uv_edge;
}
}
BLI_assert_unreachable();
return nullptr;
}
const bool contains_uv_vertex(const UVVertex *uv_vertex) const
{
for (UVEdge *edge : edges) {
if (std::find(edge->vertices.begin(), edge->vertices.end(), uv_vertex) !=
edge->vertices.end()) {
return true;
}
}
return false;
}
const UVVertex *get_other_uv_vertex(const UVVertex *v1, const UVVertex *v2) const
{
BLI_assert(contains_uv_vertex(v1));
BLI_assert(contains_uv_vertex(v2));
for (const UVEdge *edge : edges) {
for (const UVVertex *uv_vertex : edge->vertices) {
if (uv_vertex != v1 && uv_vertex != v2) {
return uv_vertex;
}
}
}
BLI_assert_unreachable();
return nullptr;
}
UVBorder extract_border() const;
};
struct UVBorderEdge {
UVEdge *edge;
bool tag = false;
UVPrimitive *uv_primitive;
/* Should the vertices of the edge be evaluated in reverse order. */
bool reverse_order = false;
int64_t index = -1;
int64_t prev_index = -1;
int64_t next_index = -1;
int64_t border_index = -1;
explicit UVBorderEdge(UVEdge *edge, UVPrimitive *uv_primitive)
: edge(edge), uv_primitive(uv_primitive)
{
}
UVVertex *get_uv_vertex(int index)
{
int actual_index = reverse_order ? 1 - index : index;
return edge->vertices[actual_index];
}
const UVVertex *get_uv_vertex(int index) const
{
int actual_index = reverse_order ? 1 - index : index;
return edge->vertices[actual_index];
}
/**
* Get the uv vertex from the primitive that is not part of the edge.
*/
const UVVertex *get_other_uv_vertex() const
{
return uv_primitive->get_other_uv_vertex(edge->vertices[0], edge->vertices[1]);
}
float length() const
{
return len_v2v2(edge->vertices[0]->uv, edge->vertices[1]->uv);
}
};
struct UVBorderCorner {
UVBorderEdge *first;
UVBorderEdge *second;
float angle;
UVBorderCorner(UVBorderEdge *first, UVBorderEdge *second, float angle)
: first(first), second(second), angle(angle)
{
}
/**
* Calculate a uv coordinate between the edges of the corner.
*
* 'min_uv_distance' is the minimum distance between the corner and the
* resulting uv coordinate. The distance is in uv space.
*/
float2 uv(float factor, float min_uv_distance);
};
struct UVBorder {
/** Ordered list of UV Verts of the border of this island. */
// TODO: support multiple rings + order (CW, CCW)
Vector<UVBorderEdge> edges;
/**
* Check if the border is counter clock wise from its island.
*/
bool is_ccw() const;
/**
* Flip the order of the verts, changing the order between CW and CCW.
*/
void flip();
/**
* Calculate the outside angle of the given vert.
*/
float outside_angle(const UVBorderEdge &vert) const;
void update_indexes(uint64_t border_index);
static std::optional<UVBorder> extract_from_edges(Vector<UVBorderEdge> &edges);
/** Remove edge from the border. updates the indexes. */
void remove(int64_t index)
{
/* Could read the border_index from any border edge as they are consistent. */
uint64_t border_index = edges[0].border_index;
edges.remove(index);
update_indexes(border_index);
}
};
struct UVIsland {
VectorList<UVVertex> uv_vertices;
VectorList<UVEdge> uv_edges;
VectorList<UVPrimitive> uv_primitives;
/**
* List of borders of this island. There can be multiple borders per island as a border could
* be completely encapsulated by another one.
*/
Vector<UVBorder> borders;
/**
* Key is mesh vert index, Value is list of UVVertices that refer to the mesh vertex with that
* index. Map is used internally to quickly lookup similar UVVertices.
*/
Map<int64_t, Vector<UVVertex *>> uv_vertex_lookup;
UVVertex *lookup(const UVVertex &vertex)
{
int64_t vert_index = vertex.vertex->v;
Vector<UVVertex *> &vertices = uv_vertex_lookup.lookup_or_add_default(vert_index);
for (UVVertex *v : vertices) {
if (v->uv == vertex.uv) {
return v;
}
}
return nullptr;
}
UVVertex *lookup_or_create(const UVVertex &vertex)
{
UVVertex *found_vertex = lookup(vertex);
if (found_vertex != nullptr) {
return found_vertex;
}
uv_vertices.append(vertex);
UVVertex *result = &uv_vertices.last();
result->uv_edges.clear();
/* v is already a key. Ensured by UVIsland::lookup in this method. */
uv_vertex_lookup.lookup(vertex.vertex->v).append(result);
return result;
}
UVEdge *lookup(const UVEdge &edge)
{
UVVertex *found_vertex = lookup(*edge.vertices[0]);
if (found_vertex == nullptr) {
return nullptr;
}
for (UVEdge *e : found_vertex->uv_edges) {
UVVertex *other_vertex = e->get_other_uv_vertex(found_vertex->vertex);
if (other_vertex->vertex == edge.vertices[1]->vertex &&
other_vertex->uv == edge.vertices[1]->uv) {
return e;
}
}
return nullptr;
}
UVEdge *lookup_or_create(const UVEdge &edge)
{
UVEdge *found_edge = lookup(edge);
if (found_edge != nullptr) {
return found_edge;
}
uv_edges.append(edge);
UVEdge *result = &uv_edges.last();
result->uv_primitives.clear();
return result;
}
/** Initialize the border attribute. */
void extract_borders();
/** Iterative extend border to fit the mask. */
void extend_border(const UVIslandsMask &mask, const short island_index);
private:
void append(const UVPrimitive &primitive)
{
uv_primitives.append(primitive);
UVPrimitive *new_prim_ptr = &uv_primitives.last();
for (int i = 0; i < 3; i++) {
UVEdge *other_edge = primitive.edges[i];
UVEdge uv_edge_template;
uv_edge_template.vertices[0] = lookup_or_create(*other_edge->vertices[0]);
uv_edge_template.vertices[1] = lookup_or_create(*other_edge->vertices[1]);
new_prim_ptr->edges[i] = lookup_or_create(uv_edge_template);
new_prim_ptr->edges[i]->append_to_uv_vertices();
new_prim_ptr->edges[i]->uv_primitives.append(new_prim_ptr);
}
}
public:
bool has_shared_edge(const UVPrimitive &primitive) const
{
for (const VectorList<UVPrimitive>::UsedVector &prims : uv_primitives) {
for (const UVPrimitive &prim : prims) {
if (prim.has_shared_edge(primitive)) {
return true;
}
}
}
return false;
}
bool has_shared_edge(const MeshPrimitive &primitive) const
{
for (const VectorList<UVPrimitive>::UsedVector &primitives : uv_primitives) {
for (const UVPrimitive &prim : primitives) {
if (prim.has_shared_edge(primitive)) {
return true;
}
}
}
return false;
}
const void extend_border(const UVPrimitive &primitive)
{
for (const VectorList<UVPrimitive>::UsedVector &primitives : uv_primitives) {
for (const UVPrimitive &prim : primitives) {
if (prim.has_shared_edge(primitive)) {
append(primitive);
}
}
}
}
};
struct UVIslands {
Vector<UVIsland> islands;
explicit UVIslands(MeshData &mesh_data);
void extract_borders();
void extend_borders(const UVIslandsMask &islands_mask);
};
/** Mask to find the index of the UVIsland for a given UV coordinate. */
struct UVIslandsMask {
/** Mask for each udim tile. */
struct Tile {
float2 udim_offset;
ushort2 tile_resolution;
ushort2 mask_resolution;
Array<uint16_t> mask;
Tile(float2 udim_offset, ushort2 tile_resolution);
bool is_masked(const uint16_t island_index, const float2 uv) const;
bool contains(const float2 uv) const;
float get_pixel_size_in_uv_space() const;
};
Vector<Tile> tiles;
void add_tile(float2 udim_offset, ushort2 resolution);
/**
* Find a tile containing the given uv coordinate.
*/
const Tile *find_tile(const float2 uv) const;
/**
* Is the given uv coordinate part of the given island_index mask.
*
* true - part of the island mask.
* false - not part of the island mask.
*/
bool is_masked(const uint16_t island_index, const float2 uv) const;
/**
* Add the given UVIslands to the mask. Tiles should be added beforehand using the 'add_tile'
* method.
*/
void add(const UVIslands &islands);
void dilate(int max_iterations);
};
} // namespace blender::bke::uv_islands

View File

@@ -250,6 +250,7 @@ set(SRC
intern/pbvh.cc
intern/pbvh_bmesh.c
intern/pbvh_pixels.cc
intern/pbvh_uv_islands.cc
intern/pointcache.c
intern/pointcloud.cc
intern/preferences.c

View File

@@ -113,6 +113,7 @@ char *BKE_paint_canvas_key_get(struct PaintModeSettings *settings, struct Object
Image *image;
ImageUser *image_user;
if (BKE_paint_canvas_image_get(settings, ob, &image, &image_user)) {
ss << ",SEAM_DIST:" << image->seamfix_iter;
ImageUser tile_user = *image_user;
LISTBASE_FOREACH (ImageTile *, image_tile, &image->tiles) {
tile_user.tile = image_tile->tile_number;

View File

@@ -947,7 +947,7 @@ void BKE_pbvh_free(PBVH *pbvh)
BLI_gset_free(node->bm_other_verts, NULL);
}
pbvh_pixels_free(node);
pbvh_node_pixels_free(node);
}
}
@@ -973,6 +973,8 @@ void BKE_pbvh_free(PBVH *pbvh)
MEM_SAFE_FREE(pbvh->vert_bitmap);
pbvh_pixels_free(pbvh);
MEM_freeN(pbvh);
}

View File

@@ -217,6 +217,8 @@ struct PBVH {
bool draw_cache_invalid;
struct PBVHGPUFormat *vbo_id;
PBVHPixels pixels;
};
/* pbvh.c */
@@ -289,7 +291,8 @@ void pbvh_bmesh_normals_update(PBVHNode **nodes, int totnode);
/* pbvh_pixels.hh */
void pbvh_pixels_free(PBVHNode *node);
void pbvh_node_pixels_free(PBVHNode *node);
void pbvh_pixels_free(PBVH *pbvh);
void pbvh_pixels_free_brush_test(PBVHNode *node);
void pbvh_free_draw_buffers(PBVH *pbvh, PBVHNode *node);

View File

@@ -20,6 +20,7 @@
#include "bmesh.h"
#include "pbvh_intern.h"
#include "pbvh_uv_islands.hh"
namespace blender::bke::pbvh::pixels {
@@ -57,8 +58,11 @@ static float2 calc_barycentric_delta_x(const ImBuf *image_buffer,
static void extract_barycentric_pixels(UDIMTilePixels &tile_data,
const ImBuf *image_buffer,
const int triangle_index,
const uv_islands::UVIslandsMask &uv_mask,
const int64_t uv_island_index,
const int64_t uv_primitive_index,
const float2 uvs[3],
const float2 tile_offset,
const int minx,
const int miny,
const int maxx,
@@ -67,7 +71,7 @@ static void extract_barycentric_pixels(UDIMTilePixels &tile_data,
for (int y = miny; y < maxy; y++) {
bool start_detected = false;
PackedPixelRow pixel_row;
pixel_row.triangle_index = triangle_index;
pixel_row.uv_primitive_index = uv_primitive_index;
pixel_row.num_pixels = 0;
int x;
@@ -77,12 +81,13 @@ static void extract_barycentric_pixels(UDIMTilePixels &tile_data,
barycentric_weights_v2(uvs[0], uvs[1], uvs[2], uv, barycentric_weights);
const bool is_inside = barycentric_inside_triangle_v2(barycentric_weights);
if (!start_detected && is_inside) {
const bool is_masked = uv_mask.is_masked(uv_island_index, uv + tile_offset);
if (!start_detected && is_inside && is_masked) {
start_detected = true;
pixel_row.start_image_coordinate = ushort2(x, y);
pixel_row.start_barycentric_coord = float2(barycentric_weights.x, barycentric_weights.y);
}
else if (start_detected && !is_inside) {
else if (start_detected && (!is_inside || !is_masked)) {
break;
}
}
@@ -95,21 +100,57 @@ static void extract_barycentric_pixels(UDIMTilePixels &tile_data,
}
}
static void init_triangles(PBVH *pbvh, PBVHNode *node, NodeData *node_data, const MLoop *mloop)
/** Update the geometry primitives of the pbvh. */
static void update_geom_primitives(PBVH &pbvh, const uv_islands::MeshData &mesh_data)
{
for (int i = 0; i < node->totprim; i++) {
const MLoopTri *lt = &pbvh->looptri[node->prim_indices[i]];
node_data->triangles.append(
int3(mloop[lt->tri[0]].v, mloop[lt->tri[1]].v, mloop[lt->tri[2]].v));
PBVHData &pbvh_data = BKE_pbvh_pixels_data_get(pbvh);
pbvh_data.clear_data();
for (const uv_islands::MeshPrimitive &mesh_primitive : mesh_data.primitives) {
pbvh_data.geom_primitives.append(int3(mesh_primitive.vertices[0].vertex->v,
mesh_primitive.vertices[1].vertex->v,
mesh_primitive.vertices[2].vertex->v));
}
}
struct UVPrimitiveLookup {
struct Entry {
uv_islands::UVPrimitive *uv_primitive;
uint64_t uv_island_index;
Entry(uv_islands::UVPrimitive *uv_primitive, uint64_t uv_island_index)
: uv_primitive(uv_primitive), uv_island_index(uv_island_index)
{
}
};
Vector<Vector<Entry>> lookup;
UVPrimitiveLookup(const uint64_t geom_primitive_len, uv_islands::UVIslands &uv_islands)
{
lookup.append_n_times(Vector<Entry>(), geom_primitive_len);
uint64_t uv_island_index = 0;
for (uv_islands::UVIsland &uv_island : uv_islands.islands) {
for (VectorList<uv_islands::UVPrimitive>::UsedVector &uv_primitives :
uv_island.uv_primitives) {
for (uv_islands::UVPrimitive &uv_primitive : uv_primitives) {
lookup[uv_primitive.primitive->index].append_as(Entry(&uv_primitive, uv_island_index));
}
}
uv_island_index++;
}
}
};
struct EncodePixelsUserData {
Image *image;
ImageUser *image_user;
PBVH *pbvh;
Vector<PBVHNode *> *nodes;
const MLoopUV *ldata_uv;
const uv_islands::UVIslandsMask *uv_masks;
/** Lookup to retrieve the UV primitives based on the primitive index. */
const UVPrimitiveLookup *uv_primitive_lookup;
};
static void do_encode_pixels(void *__restrict userdata,
@@ -119,9 +160,10 @@ static void do_encode_pixels(void *__restrict userdata,
EncodePixelsUserData *data = static_cast<EncodePixelsUserData *>(userdata);
Image *image = data->image;
ImageUser image_user = *data->image_user;
PBVH *pbvh = data->pbvh;
PBVHNode *node = (*data->nodes)[n];
NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
const uv_islands::UVIslandsMask &uv_masks = *data->uv_masks;
LISTBASE_FOREACH (ImageTile *, tile, &data->image->tiles) {
image::ImageTileWrapper image_tile(tile);
image_user.tile = image_tile.get_tile_number();
@@ -130,40 +172,59 @@ static void do_encode_pixels(void *__restrict userdata,
continue;
}
float2 tile_offset = float2(image_tile.get_tile_offset());
UDIMTilePixels tile_data;
tile_data.tile_number = image_tile.get_tile_number();
float2 tile_offset = float2(image_tile.get_tile_offset());
Triangles &triangles = node_data->triangles;
for (int triangle_index = 0; triangle_index < triangles.size(); triangle_index++) {
const MLoopTri *lt = &pbvh->looptri[node->prim_indices[triangle_index]];
float2 uvs[3] = {
float2(data->ldata_uv[lt->tri[0]].uv) - tile_offset,
float2(data->ldata_uv[lt->tri[1]].uv) - tile_offset,
float2(data->ldata_uv[lt->tri[2]].uv) - tile_offset,
};
for (int pbvh_node_prim_index = 0; pbvh_node_prim_index < node->totprim;
pbvh_node_prim_index++) {
int64_t geom_prim_index = node->prim_indices[pbvh_node_prim_index];
for (const UVPrimitiveLookup::Entry &entry :
data->uv_primitive_lookup->lookup[geom_prim_index]) {
uv_islands::UVBorder uv_border = entry.uv_primitive->extract_border();
float2 uvs[3] = {
entry.uv_primitive->get_uv_vertex(0)->uv - tile_offset,
entry.uv_primitive->get_uv_vertex(1)->uv - tile_offset,
entry.uv_primitive->get_uv_vertex(2)->uv - tile_offset,
};
const float minv = clamp_f(min_fff(uvs[0].y, uvs[1].y, uvs[2].y), 0.0f, 1.0f);
const int miny = floor(minv * image_buffer->y);
const float maxv = clamp_f(max_fff(uvs[0].y, uvs[1].y, uvs[2].y), 0.0f, 1.0f);
const int maxy = min_ii(ceil(maxv * image_buffer->y), image_buffer->y);
const float minu = clamp_f(min_fff(uvs[0].x, uvs[1].x, uvs[2].x), 0.0f, 1.0f);
const int minx = floor(minu * image_buffer->x);
const float maxu = clamp_f(max_fff(uvs[0].x, uvs[1].x, uvs[2].x), 0.0f, 1.0f);
const int maxx = min_ii(ceil(maxu * image_buffer->x), image_buffer->x);
const float minv = clamp_f(min_fff(uvs[0].y, uvs[1].y, uvs[2].y), 0.0f, 1.0f);
const int miny = floor(minv * image_buffer->y);
const float maxv = clamp_f(max_fff(uvs[0].y, uvs[1].y, uvs[2].y), 0.0f, 1.0f);
const int maxy = min_ii(ceil(maxv * image_buffer->y), image_buffer->y);
const float minu = clamp_f(min_fff(uvs[0].x, uvs[1].x, uvs[2].x), 0.0f, 1.0f);
const int minx = floor(minu * image_buffer->x);
const float maxu = clamp_f(max_fff(uvs[0].x, uvs[1].x, uvs[2].x), 0.0f, 1.0f);
const int maxx = min_ii(ceil(maxu * image_buffer->x), image_buffer->x);
/* TODO: Perform bounds check */
int64_t uv_prim_index = node_data->uv_primitives.size();
node_data->uv_primitives.append(geom_prim_index);
UVPrimitivePaintInput &paint_input = node_data->uv_primitives.last();
TrianglePaintInput &triangle = triangles.get_paint_input(triangle_index);
triangle.delta_barycentric_coord_u = calc_barycentric_delta_x(image_buffer, uvs, minx, miny);
extract_barycentric_pixels(
tile_data, image_buffer, triangle_index, uvs, minx, miny, maxx, maxy);
/* Calculate barycentric delta */
paint_input.delta_barycentric_coord_u = calc_barycentric_delta_x(
image_buffer, uvs, minx, miny);
/* Extract the pixels. */
extract_barycentric_pixels(tile_data,
image_buffer,
uv_masks,
entry.uv_island_index,
uv_prim_index,
uvs,
tile_offset,
minx,
miny,
maxx,
maxy);
}
}
BKE_image_release_ibuf(image, image_buffer, nullptr);
if (tile_data.pixel_rows.is_empty()) {
continue;
}
tile_data.tile_number = image_tile.get_tile_number();
node_data->tiles.append(tile_data);
}
}
@@ -211,6 +272,16 @@ static bool find_nodes_to_update(PBVH *pbvh, Vector<PBVHNode *> &r_nodes_to_upda
return false;
}
/* Init or reset PBVH pixel data when changes detected. */
if (pbvh->pixels.data == nullptr) {
PBVHData *pbvh_data = MEM_new<PBVHData>(__func__);
pbvh->pixels.data = pbvh_data;
}
else {
PBVHData *pbvh_data = static_cast<PBVHData *>(pbvh->pixels.data);
pbvh_data->clear_data();
}
r_nodes_to_update.reserve(nodes_to_update_len);
for (int n = 0; n < pbvh->totnode; n++) {
@@ -290,11 +361,31 @@ static void update_pixels(PBVH *pbvh, Mesh *mesh, Image *image, ImageUser *image
return;
}
for (PBVHNode *node : nodes_to_update) {
NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
const Span<MLoop> loops = mesh->loops();
init_triangles(pbvh, node, node_data, loops.data());
uv_islands::MeshData mesh_data(
pbvh->looptri, pbvh->totprim, pbvh->totvert, pbvh->mloop, ldata_uv);
uv_islands::UVIslands islands(mesh_data);
uv_islands::UVIslandsMask uv_masks;
ImageUser tile_user = *image_user;
LISTBASE_FOREACH (ImageTile *, tile_data, &image->tiles) {
image::ImageTileWrapper image_tile(tile_data);
tile_user.tile = image_tile.get_tile_number();
ImBuf *tile_buffer = BKE_image_acquire_ibuf(image, &tile_user, nullptr);
if (tile_buffer == nullptr) {
continue;
}
uv_masks.add_tile(float2(image_tile.get_tile_x_offset(), image_tile.get_tile_y_offset()),
ushort2(tile_buffer->x, tile_buffer->y));
BKE_image_release_ibuf(image, tile_buffer, nullptr);
}
uv_masks.add(islands);
uv_masks.dilate(image->seamfix_iter);
islands.extract_borders();
islands.extend_borders(uv_masks);
update_geom_primitives(*pbvh, mesh_data);
UVPrimitiveLookup uv_primitive_lookup(mesh_data.looptri_len, islands);
EncodePixelsUserData user_data;
user_data.pbvh = pbvh;
@@ -302,6 +393,8 @@ static void update_pixels(PBVH *pbvh, Mesh *mesh, Image *image, ImageUser *image
user_data.image_user = image_user;
user_data.ldata_uv = ldata_uv;
user_data.nodes = &nodes_to_update;
user_data.uv_primitive_lookup = &uv_primitive_lookup;
user_data.uv_masks = &uv_masks;
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, nodes_to_update.size());
@@ -356,6 +449,13 @@ NodeData &BKE_pbvh_pixels_node_data_get(PBVHNode &node)
return *node_data;
}
PBVHData &BKE_pbvh_pixels_data_get(PBVH &pbvh)
{
BLI_assert(pbvh.pixels.data != nullptr);
PBVHData *data = static_cast<PBVHData *>(pbvh.pixels.data);
return *data;
}
void BKE_pbvh_pixels_mark_image_dirty(PBVHNode &node, Image &image, ImageUser &image_user)
{
BLI_assert(node.pixels.node_data != nullptr);
@@ -387,10 +487,17 @@ void BKE_pbvh_build_pixels(PBVH *pbvh, Mesh *mesh, Image *image, ImageUser *imag
update_pixels(pbvh, mesh, image, image_user);
}
void pbvh_pixels_free(PBVHNode *node)
void pbvh_node_pixels_free(PBVHNode *node)
{
NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
MEM_delete(node_data);
node->pixels.node_data = nullptr;
}
void pbvh_pixels_free(PBVH *pbvh)
{
PBVHData *pbvh_data = static_cast<PBVHData *>(pbvh->pixels.data);
MEM_delete(pbvh_data);
pbvh->pixels.data = nullptr;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,334 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*
* UV Islands for PBVH Pixel extraction. When primitives share an edge they belong to the same UV
* Island.
*
* \note Similar to `uvedit_islands.cc`, but optimized for PBVH painting without using BMesh for
* performance reasons. Non-manifold meshes only (i.e. edges must have less than 3 faces).
*
* Polygons (face with more than 3 edges) are supported as they are split up to primitives.
*
* \note After the algorithm is stable the OO data structures should be converted back to use DOD
* principles to improve reusability. Currently this is not done (yet) as during implementation it
* was hard to follow when the algorithm evolved during several iterations. At that time we needed
* more flexibility.
*/
#pragma once
#include <fstream>
#include <optional>
#include "BLI_array.hh"
#include "BLI_edgehash.h"
#include "BLI_float3x3.hh"
#include "BLI_map.hh"
#include "BLI_math.h"
#include "BLI_math_vec_types.hh"
#include "BLI_rect.h"
#include "BLI_vector.hh"
#include "BLI_vector_list.hh"
#include "DNA_meshdata_types.h"
namespace blender::bke::pbvh::uv_islands {
struct MeshEdge;
struct MeshPrimitive;
struct UVBorder;
struct UVEdge;
struct UVIslands;
struct UVIslandsMask;
struct UVPrimitive;
struct UVPrimitiveEdge;
struct UVVertex;
struct MeshVertex {
int64_t v;
Vector<MeshEdge *> edges;
};
struct MeshUVVert {
MeshVertex *vertex;
float2 uv;
int64_t loop;
};
struct MeshEdge {
MeshVertex *vert1;
MeshVertex *vert2;
Vector<MeshPrimitive *> primitives;
};
/** Represents a triangle in 3d space (MLoopTri). */
struct MeshPrimitive {
int64_t index;
int64_t poly;
Vector<MeshEdge *, 3> edges;
Vector<MeshUVVert, 3> vertices;
/**
* UV island this primitive belongs to. This is used to speed up the initial uv island
* extraction and should not be used afterwards.
*/
int64_t uv_island_id;
/** Get the vertex that is not given. Both given vertices must be part of the MeshPrimitive. */
MeshUVVert *get_other_uv_vertex(const MeshVertex *v1, const MeshVertex *v2);
/** Get the UV bounds for this MeshPrimitive. */
rctf uv_bounds() const;
/** Is the given MeshPrimitive sharing an edge. */
bool has_shared_uv_edge(const MeshPrimitive *other) const;
};
/**
* MeshData contains input geometry data converted in a list of primitives, edges and vertices for
* quick access for both local space and uv space.
*/
struct MeshData {
public:
const MLoopTri *looptri;
const int64_t looptri_len;
const int64_t vert_len;
const MLoop *mloop;
const MLoopUV *mloopuv;
Vector<MeshPrimitive> primitives;
Vector<MeshEdge> edges;
Vector<MeshVertex> vertices;
/** Total number of found uv islands. */
int64_t uv_island_len;
public:
explicit MeshData(const MLoopTri *looptri,
const int64_t looptri_len,
const int64_t vert_len,
const MLoop *mloop,
const MLoopUV *mloopuv);
};
struct UVVertex {
MeshVertex *vertex;
/* Position in uv space. */
float2 uv;
/* uv edges that share this UVVertex. */
Vector<UVEdge *> uv_edges;
struct {
bool is_border : 1;
bool is_extended : 1;
} flags;
explicit UVVertex();
explicit UVVertex(const MeshUVVert &vert);
};
struct UVEdge {
std::array<UVVertex *, 2> vertices;
Vector<UVPrimitive *, 2> uv_primitives;
UVVertex *get_other_uv_vertex(const MeshVertex *vertex);
bool has_shared_edge(const MeshUVVert &v1, const MeshUVVert &v2) const;
bool has_shared_edge(const UVEdge &other) const;
bool has_same_vertices(const MeshEdge &edge) const;
bool is_border_edge() const;
private:
bool has_shared_edge(const UVVertex &v1, const UVVertex &v2) const;
bool has_same_vertices(const MeshVertex &vert1, const MeshVertex &vert2) const;
bool has_same_uv_vertices(const UVEdge &other) const;
};
struct UVPrimitive {
/**
* Index of the primitive in the original mesh.
*/
MeshPrimitive *primitive;
Vector<UVEdge *, 3> edges;
explicit UVPrimitive(MeshPrimitive *primitive);
Vector<std::pair<UVEdge *, UVEdge *>> shared_edges(UVPrimitive &other);
bool has_shared_edge(const UVPrimitive &other) const;
bool has_shared_edge(const MeshPrimitive &primitive) const;
/**
* Get the UVVertex in the order that the verts are ordered in the MeshPrimitive.
*/
const UVVertex *get_uv_vertex(const uint8_t mesh_vert_index) const;
/**
* Get the UVEdge that share the given uv coordinates.
* Will assert when no UVEdge found.
*/
UVEdge *get_uv_edge(const float2 uv1, const float2 uv2) const;
UVEdge *get_uv_edge(const MeshVertex *v1, const MeshVertex *v2) const;
const bool contains_uv_vertex(const UVVertex *uv_vertex) const;
const UVVertex *get_other_uv_vertex(const UVVertex *v1, const UVVertex *v2) const;
UVBorder extract_border() const;
};
struct UVBorderEdge {
UVEdge *edge;
bool tag = false;
UVPrimitive *uv_primitive;
/* Should the vertices of the edge be evaluated in reverse order. */
bool reverse_order = false;
int64_t index = -1;
int64_t prev_index = -1;
int64_t next_index = -1;
int64_t border_index = -1;
explicit UVBorderEdge(UVEdge *edge, UVPrimitive *uv_primitive);
UVVertex *get_uv_vertex(int index);
const UVVertex *get_uv_vertex(int index) const;
/**
* Get the uv vertex from the primitive that is not part of the edge.
*/
const UVVertex *get_other_uv_vertex() const;
float length() const;
};
struct UVBorderCorner {
UVBorderEdge *first;
UVBorderEdge *second;
float angle;
explicit UVBorderCorner(UVBorderEdge *first, UVBorderEdge *second, float angle);
/**
* Calculate a uv coordinate between the edges of the corner.
*
* 'min_uv_distance' is the minimum distance between the corner and the
* resulting uv coordinate. The distance is in uv space.
*/
float2 uv(float factor, float min_uv_distance);
};
struct UVBorder {
/** Ordered list of UV Verts of the border of this island. */
Vector<UVBorderEdge> edges;
/**
* Check if the border is counter clock wise from its island.
*/
bool is_ccw() const;
/**
* Flip the order of the verts, changing the order between CW and CCW.
*/
void flip_order();
/**
* Calculate the outside angle of the given vert.
*/
float outside_angle(const UVBorderEdge &vert) const;
void update_indexes(uint64_t border_index);
static std::optional<UVBorder> extract_from_edges(Vector<UVBorderEdge> &edges);
/** Remove edge from the border. updates the indexes. */
void remove(int64_t index);
};
struct UVIsland {
VectorList<UVVertex> uv_vertices;
VectorList<UVEdge> uv_edges;
VectorList<UVPrimitive> uv_primitives;
/**
* List of borders of this island. There can be multiple borders per island as a border could
* be completely encapsulated by another one.
*/
Vector<UVBorder> borders;
/**
* Key is mesh vert index, Value is list of UVVertices that refer to the mesh vertex with that
* index. Map is used internally to quickly lookup similar UVVertices.
*/
Map<int64_t, Vector<UVVertex *>> uv_vertex_lookup;
UVVertex *lookup(const UVVertex &vertex);
UVVertex *lookup_or_create(const UVVertex &vertex);
UVEdge *lookup(const UVEdge &edge);
UVEdge *lookup_or_create(const UVEdge &edge);
/** Initialize the border attribute. */
void extract_borders();
/** Iterative extend border to fit the mask. */
void extend_border(const UVIslandsMask &mask, const short island_index);
private:
void append(const UVPrimitive &primitive);
public:
bool has_shared_edge(const UVPrimitive &primitive) const;
bool has_shared_edge(const MeshPrimitive &primitive) const;
void extend_border(const UVPrimitive &primitive);
};
struct UVIslands {
Vector<UVIsland> islands;
explicit UVIslands(MeshData &mesh_data);
void extract_borders();
void extend_borders(const UVIslandsMask &islands_mask);
};
/** Mask to find the index of the UVIsland for a given UV coordinate. */
struct UVIslandsMask {
/** Mask for each udim tile. */
struct Tile {
float2 udim_offset;
ushort2 tile_resolution;
ushort2 mask_resolution;
Array<uint16_t> mask;
Tile(float2 udim_offset, ushort2 tile_resolution);
bool is_masked(const uint16_t island_index, const float2 uv) const;
bool contains(const float2 uv) const;
float get_pixel_size_in_uv_space() const;
};
Vector<Tile> tiles;
void add_tile(float2 udim_offset, ushort2 resolution);
/**
* Find a tile containing the given uv coordinate.
*/
const Tile *find_tile(const float2 uv) const;
/**
* Is the given uv coordinate part of the given island_index mask.
*
* true - part of the island mask.
* false - not part of the island mask.
*/
bool is_masked(const uint16_t island_index, const float2 uv) const;
/**
* Add the given UVIslands to the mask. Tiles should be added beforehand using the 'add_tile'
* method.
*/
void add(const UVIslands &islands);
void dilate(int max_iterations);
};
} // namespace blender::bke::pbvh::uv_islands

View File

@@ -918,6 +918,11 @@ class Vector {
return int64_t(capacity_end_ - begin_);
}
bool is_at_capacity() const
{
return end_ == capacity_end_;
}
/**
* Get an index range that makes looping over all indices more convenient and less error prone.
* Obviously, this should only be used when you actually need the index in the loop.

View File

@@ -0,0 +1,120 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <algorithm>
#include "BLI_vector.hh"
namespace blender {
/**
* A VectorList is a vector of vectors.
*
* VectorList can be used when:
*
* 1) Don't know up front the number of elements that will be added to the list. Use array or
* vector.reserve when known up front.
*
* 2) Number of reads/writes doesn't require sequential access
* of the whole list. A vector ensures memory is sequential which is fast when reading, writing can
* have overhead when the reserved memory is full.
*
* When a VectorList reserved memory is full it will allocate memory for the new items, breaking
* the sequential access. Within each allocated memory block the elements are ordered sequentially.
*/
template<typename T, int64_t CapacityStart = 32, int64_t CapacitySoftLimit = 4096>
class VectorList {
public:
using UsedVector = Vector<T, 0>;
private:
/**
* Contains the individual vectors. There must always be at least one vector
*/
Vector<UsedVector> vectors_;
public:
VectorList()
{
this->append_vector();
}
void append(const T &value)
{
this->append_as(value);
}
void append(T &&value)
{
this->append_as(std::move(value));
}
template<typename ForwardT> void append_as(ForwardT &&value)
{
UsedVector &vector = this->ensure_space_for_one();
vector.append_unchecked_as(std::forward<ForwardT>(value));
}
UsedVector *begin()
{
return vectors_.begin();
}
UsedVector *end()
{
return vectors_.end();
}
const UsedVector *begin() const
{
return vectors_.begin();
}
const UsedVector *end() const
{
return vectors_.end();
}
T &last()
{
return vectors_.last().last();
}
int64_t size() const
{
int64_t result = 0;
for (const UsedVector &vector : *this) {
result += vector.size();
}
return result;
}
private:
UsedVector &ensure_space_for_one()
{
UsedVector &vector = vectors_.last();
if (LIKELY(!vector.is_at_capacity())) {
return vector;
}
this->append_vector();
return vectors_.last();
}
void append_vector()
{
const int64_t new_vector_capacity = this->get_next_vector_capacity();
vectors_.append({});
vectors_.last().reserve(new_vector_capacity);
}
int64_t get_next_vector_capacity()
{
if (vectors_.is_empty()) {
return CapacityStart;
}
return std::min(vectors_.last().capacity() * 2, CapacitySoftLimit);
}
};
} // namespace blender

View File

@@ -340,6 +340,7 @@ set(SRC
BLI_uuid.h
BLI_uvproject.h
BLI_vector.hh
BLI_vector_list.hh
BLI_vector_set.hh
BLI_vector_set_slots.hh
BLI_virtual_array.hh

View File

@@ -3711,5 +3711,12 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain)
*/
{
/* Keep this block, even when empty. */
/* UVSeam fixing distance. */
if (!DNA_struct_elem_find(fd->filesdna, "Image", "short", "seamfix_iter")) {
LISTBASE_FOREACH (Image *, image, &bmain->images) {
image->seamfix_iter = 8;
}
}
}
}

View File

@@ -146,15 +146,18 @@ template<typename ImageBuffer> class PaintingKernel {
init_brush_test();
}
bool paint(const Triangles &triangles,
bool paint(const PaintGeometryPrimitives &geom_primitives,
const PaintUVPrimitives &uv_primitives,
const PackedPixelRow &pixel_row,
ImBuf *image_buffer,
AutomaskingNodeData *automask_data)
{
image_accessor.set_image_position(image_buffer, pixel_row.start_image_coordinate);
const TrianglePaintInput triangle = triangles.get_paint_input(pixel_row.triangle_index);
float3 pixel_pos = get_start_pixel_pos(triangle, pixel_row);
const float3 delta_pixel_pos = get_delta_pixel_pos(triangle, pixel_row, pixel_pos);
const UVPrimitivePaintInput paint_input = uv_primitives.get_paint_input(
pixel_row.uv_primitive_index);
float3 pixel_pos = get_start_pixel_pos(geom_primitives, paint_input, pixel_row);
const float3 delta_pixel_pos = get_delta_pixel_pos(
geom_primitives, paint_input, pixel_row, pixel_pos);
bool pixels_painted = false;
for (int x = 0; x < pixel_row.num_pixels; x++) {
if (!brush_test_fn(&test, pixel_pos)) {
@@ -231,29 +234,35 @@ template<typename ImageBuffer> class PaintingKernel {
/**
* Extract the starting pixel position from the given encoded_pixels belonging to the triangle.
*/
float3 get_start_pixel_pos(const TrianglePaintInput &triangle,
float3 get_start_pixel_pos(const PaintGeometryPrimitives &geom_primitives,
const UVPrimitivePaintInput &paint_input,
const PackedPixelRow &encoded_pixels) const
{
return init_pixel_pos(triangle, encoded_pixels.start_barycentric_coord);
return init_pixel_pos(geom_primitives, paint_input, encoded_pixels.start_barycentric_coord);
}
/**
* Extract the delta pixel position that will be used to advance a Pixel instance to the next
* pixel.
*/
float3 get_delta_pixel_pos(const TrianglePaintInput &triangle,
float3 get_delta_pixel_pos(const PaintGeometryPrimitives &geom_primitives,
const UVPrimitivePaintInput &paint_input,
const PackedPixelRow &encoded_pixels,
const float3 &start_pixel) const
{
float3 result = init_pixel_pos(
triangle, encoded_pixels.start_barycentric_coord + triangle.delta_barycentric_coord_u);
float3 result = init_pixel_pos(geom_primitives,
paint_input,
encoded_pixels.start_barycentric_coord +
paint_input.delta_barycentric_coord_u);
return result - start_pixel;
}
float3 init_pixel_pos(const TrianglePaintInput &triangle,
float3 init_pixel_pos(const PaintGeometryPrimitives &geom_primitives,
const UVPrimitivePaintInput &paint_input,
const float2 &barycentric_weights) const
{
const int3 &vert_indices = triangle.vert_indices;
const int3 &vert_indices = geom_primitives.get_vert_indices(
paint_input.geometry_primitive_index);
float3 result;
const float3 barycentric(barycentric_weights.x,
barycentric_weights.y,
@@ -267,11 +276,12 @@ template<typename ImageBuffer> class PaintingKernel {
}
};
static std::vector<bool> init_triangle_brush_test(SculptSession *ss,
Triangles &triangles,
const MVert *mvert)
static std::vector<bool> init_uv_primitives_brush_test(SculptSession *ss,
PaintGeometryPrimitives &geom_primitives,
PaintUVPrimitives &uv_primitives,
const MVert *mvert)
{
std::vector<bool> brush_test(triangles.size());
std::vector<bool> brush_test(uv_primitives.size());
SculptBrushTest test;
SCULPT_brush_test_init(ss, &test);
float3 brush_min_bounds(test.location[0] - test.radius,
@@ -280,13 +290,15 @@ static std::vector<bool> init_triangle_brush_test(SculptSession *ss,
float3 brush_max_bounds(test.location[0] + test.radius,
test.location[1] + test.radius,
test.location[2] + test.radius);
for (int triangle_index = 0; triangle_index < triangles.size(); triangle_index++) {
TrianglePaintInput &triangle = triangles.get_paint_input(triangle_index);
for (int uv_prim_index = 0; uv_prim_index < uv_primitives.size(); uv_prim_index++) {
const UVPrimitivePaintInput &paint_input = uv_primitives.get_paint_input(uv_prim_index);
const int3 &vert_indices = geom_primitives.get_vert_indices(
paint_input.geometry_primitive_index);
float3 triangle_min_bounds(mvert[triangle.vert_indices[0]].co);
float3 triangle_min_bounds(mvert[vert_indices[0]].co);
float3 triangle_max_bounds(triangle_min_bounds);
for (int i = 1; i < 3; i++) {
const float3 &pos = mvert[triangle.vert_indices[i]].co;
const float3 &pos = mvert[vert_indices[i]].co;
triangle_min_bounds.x = min_ff(triangle_min_bounds.x, pos.x);
triangle_min_bounds.y = min_ff(triangle_min_bounds.y, pos.y);
triangle_min_bounds.z = min_ff(triangle_min_bounds.z, pos.z);
@@ -294,7 +306,7 @@ static std::vector<bool> init_triangle_brush_test(SculptSession *ss,
triangle_max_bounds.y = max_ff(triangle_max_bounds.y, pos.y);
triangle_max_bounds.z = max_ff(triangle_max_bounds.z, pos.z);
}
brush_test[triangle_index] = isect_aabb_aabb_v3(
brush_test[uv_prim_index] = isect_aabb_aabb_v3(
brush_min_bounds, brush_max_bounds, triangle_min_bounds, triangle_max_bounds);
}
return brush_test;
@@ -308,13 +320,15 @@ static void do_paint_pixels(void *__restrict userdata,
Object *ob = data->ob;
SculptSession *ss = ob->sculpt;
const Brush *brush = data->brush;
PBVH *pbvh = ss->pbvh;
PBVHNode *node = data->nodes[n];
PBVHData &pbvh_data = BKE_pbvh_pixels_data_get(*pbvh);
NodeData &node_data = BKE_pbvh_pixels_node_data_get(*node);
const int thread_id = BLI_task_parallel_thread_id(tls);
MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss);
std::vector<bool> brush_test = init_triangle_brush_test(ss, node_data.triangles, mvert);
std::vector<bool> brush_test = init_uv_primitives_brush_test(
ss, pbvh_data.geom_primitives, node_data.uv_primitives, mvert);
PaintingKernel<ImageBufferFloat4> kernel_float4(ss, brush, thread_id, mvert);
PaintingKernel<ImageBufferByte4> kernel_byte4(ss, brush, thread_id, mvert);
@@ -343,17 +357,17 @@ static void do_paint_pixels(void *__restrict userdata,
}
for (const PackedPixelRow &pixel_row : tile_data.pixel_rows) {
if (!brush_test[pixel_row.triangle_index]) {
if (!brush_test[pixel_row.uv_primitive_index]) {
continue;
}
bool pixels_painted = false;
if (image_buffer->rect_float != nullptr) {
pixels_painted = kernel_float4.paint(
node_data.triangles, pixel_row, image_buffer, &automask_data);
pbvh_data.geom_primitives, node_data.uv_primitives, pixel_row, image_buffer, &automask_data);
}
else {
pixels_painted = kernel_byte4.paint(
node_data.triangles, pixel_row, image_buffer, &automask_data);
pbvh_data.geom_primitives, node_data.uv_primitives, pixel_row, image_buffer, &automask_data);
}
if (pixels_painted) {

View File

@@ -952,6 +952,7 @@ void uiTemplateImage(uiLayout *layout,
}
uiItemR(col, &imaptr, "use_view_as_render", 0, NULL, ICON_NONE);
uiItemR(col, &imaptr, "seamfix_iter", 0, NULL, ICON_NONE);
}
}

View File

@@ -24,6 +24,7 @@
.gpuframenr = INT_MAX, \
.gpu_pass = SHRT_MAX, \
.gpu_layer = SHRT_MAX, \
.seamfix_iter = 8, \
}
/** \} */

View File

@@ -163,7 +163,11 @@ typedef struct Image {
short gpu_pass;
short gpu_layer;
short gpu_view;
char _pad2[4];
/* Number of iterations to perform to extract a mask for uv seam fixing. */
short seamfix_iter;
char _pad2[2];
/** Deprecated. */
struct PackedFile *packedfile DNA_DEPRECATED;

View File

@@ -1373,6 +1373,14 @@ static void rna_def_image(BlenderRNA *brna)
"Use 16 bits per channel to lower the memory usage during rendering");
RNA_def_property_update(prop, NC_IMAGE | ND_DISPLAY, "rna_Image_gpu_texture_update");
prop = RNA_def_property(srna, "seamfix_iter", PROP_INT, PROP_NONE);
RNA_def_property_ui_text(
prop,
"Seam-fix",
"Number of dilate iterations when extracting masks for UV Islands. Higher "
"number would improve seam-fixes for mipmaps, but decreases performance");
RNA_def_property_ui_range(prop, 1, 100, 1, 1);
/* multiview */
prop = RNA_def_property(srna, "views_format", PROP_ENUM, PROP_NONE);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);