Merge branch 'master' into temp_bmesh_multires
This commit is contained in:
commit
9bea7259e1
|
@ -4,7 +4,9 @@ Simple Render Engine
|
|||
"""
|
||||
|
||||
import bpy
|
||||
import bgl
|
||||
import array
|
||||
import gpu
|
||||
from gpu_extras.presets import draw_texture_2d
|
||||
|
||||
|
||||
class CustomRenderEngine(bpy.types.RenderEngine):
|
||||
|
@ -100,8 +102,7 @@ class CustomRenderEngine(bpy.types.RenderEngine):
|
|||
dimensions = region.width, region.height
|
||||
|
||||
# Bind shader that converts from scene linear to display space,
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
bgl.glBlendFunc(bgl.GL_ONE, bgl.GL_ONE_MINUS_SRC_ALPHA)
|
||||
gpu.state.blend_set('ALPHA_PREMULT')
|
||||
self.bind_display_space_shader(scene)
|
||||
|
||||
if not self.draw_data or self.draw_data.dimensions != dimensions:
|
||||
|
@ -110,7 +111,7 @@ class CustomRenderEngine(bpy.types.RenderEngine):
|
|||
self.draw_data.draw()
|
||||
|
||||
self.unbind_display_space_shader()
|
||||
bgl.glDisable(bgl.GL_BLEND)
|
||||
gpu.state.blend_set('NONE')
|
||||
|
||||
|
||||
class CustomDrawData:
|
||||
|
@ -119,68 +120,21 @@ class CustomDrawData:
|
|||
self.dimensions = dimensions
|
||||
width, height = dimensions
|
||||
|
||||
pixels = [0.1, 0.2, 0.1, 1.0] * width * height
|
||||
pixels = bgl.Buffer(bgl.GL_FLOAT, width * height * 4, pixels)
|
||||
pixels = width * height * array.array('f', [0.1, 0.2, 0.1, 1.0])
|
||||
pixels = gpu.types.Buffer('FLOAT', width * height * 4, pixels)
|
||||
|
||||
# Generate texture
|
||||
self.texture = bgl.Buffer(bgl.GL_INT, 1)
|
||||
bgl.glGenTextures(1, self.texture)
|
||||
bgl.glActiveTexture(bgl.GL_TEXTURE0)
|
||||
bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.texture[0])
|
||||
bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, bgl.GL_RGBA16F, width, height, 0, bgl.GL_RGBA, bgl.GL_FLOAT, pixels)
|
||||
bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_LINEAR)
|
||||
bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_LINEAR)
|
||||
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
|
||||
self.texture = gpu.types.GPUTexture((width, height), format='RGBA16F', data=pixels)
|
||||
|
||||
# Bind shader that converts from scene linear to display space,
|
||||
# use the scene's color management settings.
|
||||
shader_program = bgl.Buffer(bgl.GL_INT, 1)
|
||||
bgl.glGetIntegerv(bgl.GL_CURRENT_PROGRAM, shader_program)
|
||||
|
||||
# Generate vertex array
|
||||
self.vertex_array = bgl.Buffer(bgl.GL_INT, 1)
|
||||
bgl.glGenVertexArrays(1, self.vertex_array)
|
||||
bgl.glBindVertexArray(self.vertex_array[0])
|
||||
|
||||
texturecoord_location = bgl.glGetAttribLocation(shader_program[0], "texCoord")
|
||||
position_location = bgl.glGetAttribLocation(shader_program[0], "pos")
|
||||
|
||||
bgl.glEnableVertexAttribArray(texturecoord_location)
|
||||
bgl.glEnableVertexAttribArray(position_location)
|
||||
|
||||
# Generate geometry buffers for drawing textured quad
|
||||
position = [0.0, 0.0, width, 0.0, width, height, 0.0, height]
|
||||
position = bgl.Buffer(bgl.GL_FLOAT, len(position), position)
|
||||
texcoord = [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0]
|
||||
texcoord = bgl.Buffer(bgl.GL_FLOAT, len(texcoord), texcoord)
|
||||
|
||||
self.vertex_buffer = bgl.Buffer(bgl.GL_INT, 2)
|
||||
|
||||
bgl.glGenBuffers(2, self.vertex_buffer)
|
||||
bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vertex_buffer[0])
|
||||
bgl.glBufferData(bgl.GL_ARRAY_BUFFER, 32, position, bgl.GL_STATIC_DRAW)
|
||||
bgl.glVertexAttribPointer(position_location, 2, bgl.GL_FLOAT, bgl.GL_FALSE, 0, None)
|
||||
|
||||
bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vertex_buffer[1])
|
||||
bgl.glBufferData(bgl.GL_ARRAY_BUFFER, 32, texcoord, bgl.GL_STATIC_DRAW)
|
||||
bgl.glVertexAttribPointer(texturecoord_location, 2, bgl.GL_FLOAT, bgl.GL_FALSE, 0, None)
|
||||
|
||||
bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, 0)
|
||||
bgl.glBindVertexArray(0)
|
||||
# Note: This is just a didactic example.
|
||||
# In this case it would be more convenient to fill the texture with:
|
||||
# self.texture.clear('FLOAT', value=[0.1, 0.2, 0.1, 1.0])
|
||||
|
||||
def __del__(self):
|
||||
bgl.glDeleteBuffers(2, self.vertex_buffer)
|
||||
bgl.glDeleteVertexArrays(1, self.vertex_array)
|
||||
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
|
||||
bgl.glDeleteTextures(1, self.texture)
|
||||
del self.texture
|
||||
|
||||
def draw(self):
|
||||
bgl.glActiveTexture(bgl.GL_TEXTURE0)
|
||||
bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.texture[0])
|
||||
bgl.glBindVertexArray(self.vertex_array[0])
|
||||
bgl.glDrawArrays(bgl.GL_TRIANGLE_FAN, 0, 4)
|
||||
bgl.glBindVertexArray(0)
|
||||
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
|
||||
draw_texture_2d(self.texture, (0, 0), self.texture.width, self.texture.height)
|
||||
|
||||
|
||||
# RenderEngines also need to tell UI Panels that they are compatible with.
|
||||
|
|
|
@ -1029,7 +1029,6 @@ def pymodule2sphinx(basepath, module_name, module, title, module_all_extra):
|
|||
context_type_map = {
|
||||
# context_member: (RNA type, is_collection)
|
||||
"active_annotation_layer": ("GPencilLayer", False),
|
||||
"active_base": ("ObjectBase", False),
|
||||
"active_bone": ("EditBone", False),
|
||||
"active_gpencil_frame": ("GreasePencilLayer", True),
|
||||
"active_gpencil_layer": ("GPencilLayer", True),
|
||||
|
@ -1549,8 +1548,8 @@ def pyrna2sphinx(basepath):
|
|||
fw(".. hlist::\n")
|
||||
fw(" :columns: 2\n\n")
|
||||
|
||||
# context does its own thing
|
||||
# "active_base": ("ObjectBase", False),
|
||||
# Context does its own thing.
|
||||
# "active_object": ("Object", False),
|
||||
for ref_attr, (ref_type, ref_is_seq) in sorted(context_type_map.items()):
|
||||
if ref_type == struct_id:
|
||||
fw(" * :mod:`bpy.context.%s`\n" % ref_attr)
|
||||
|
|
|
@ -322,7 +322,9 @@ static bool clg_ctx_filter_check(CLogContext *ctx, const char *identifier)
|
|||
if (flt->match[0] == '*' && flt->match[len - 1] == '*') {
|
||||
char *match = MEM_callocN(sizeof(char) * len - 1, __func__);
|
||||
memcpy(match, flt->match + 1, len - 2);
|
||||
if (strstr(identifier, match) != NULL) {
|
||||
const bool success = (strstr(identifier, match) != NULL);
|
||||
MEM_freeN(match);
|
||||
if (success) {
|
||||
return (bool)i;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,16 +19,16 @@ from __future__ import annotations
|
|||
|
||||
|
||||
def _is_using_buggy_driver():
|
||||
import bgl
|
||||
import gpu
|
||||
# We need to be conservative here because in multi-GPU systems display card
|
||||
# might be quite old, but others one might be just good.
|
||||
#
|
||||
# So We shouldn't disable possible good dedicated cards just because display
|
||||
# card seems weak. And instead we only blacklist configurations which are
|
||||
# proven to cause problems.
|
||||
if bgl.glGetString(bgl.GL_VENDOR) == "ATI Technologies Inc.":
|
||||
if gpu.platform.vendor_get() == "ATI Technologies Inc.":
|
||||
import re
|
||||
version = bgl.glGetString(bgl.GL_VERSION)
|
||||
version = gpu.platform.version_get()
|
||||
if version.endswith("Compatibility Profile Context"):
|
||||
# Old HD 4xxx and 5xxx series drivers did not have driver version
|
||||
# in the version string, but those cards do not quite work and
|
||||
|
|
|
@ -57,14 +57,24 @@ ccl_device ccl_addr_space void *closure_alloc_extra(ShaderData *sd, int size)
|
|||
|
||||
ccl_device_inline ShaderClosure *bsdf_alloc(ShaderData *sd, int size, float3 weight)
|
||||
{
|
||||
ShaderClosure *sc = closure_alloc(sd, size, CLOSURE_NONE_ID, weight);
|
||||
kernel_assert(isfinite3_safe(weight));
|
||||
|
||||
if (sc == NULL)
|
||||
return NULL;
|
||||
const float sample_weight = fabsf(average(weight));
|
||||
|
||||
float sample_weight = fabsf(average(weight));
|
||||
sc->sample_weight = sample_weight;
|
||||
return (sample_weight >= CLOSURE_WEIGHT_CUTOFF) ? sc : NULL;
|
||||
/* Use comparison this way to help dealing with non-finite weight: if the average is not finite
|
||||
* we will not allocate new closure. */
|
||||
if (sample_weight >= CLOSURE_WEIGHT_CUTOFF) {
|
||||
ShaderClosure *sc = closure_alloc(sd, size, CLOSURE_NONE_ID, weight);
|
||||
if (sc == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sc->sample_weight = sample_weight;
|
||||
|
||||
return sc;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef __OSL__
|
||||
|
@ -73,17 +83,27 @@ ccl_device_inline ShaderClosure *bsdf_alloc_osl(ShaderData *sd,
|
|||
float3 weight,
|
||||
void *data)
|
||||
{
|
||||
ShaderClosure *sc = closure_alloc(sd, size, CLOSURE_NONE_ID, weight);
|
||||
kernel_assert(isfinite3_safe(weight));
|
||||
|
||||
if (!sc)
|
||||
return NULL;
|
||||
const float sample_weight = fabsf(average(weight));
|
||||
|
||||
memcpy((void *)sc, data, size);
|
||||
/* Use comparison this way to help dealing with non-finite weight: if the average is not finite
|
||||
* we will not allocate new closure. */
|
||||
if (sample_weight >= CLOSURE_WEIGHT_CUTOFF) {
|
||||
ShaderClosure *sc = closure_alloc(sd, size, CLOSURE_NONE_ID, weight);
|
||||
if (!sc) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
float sample_weight = fabsf(average(weight));
|
||||
sc->weight = weight;
|
||||
sc->sample_weight = sample_weight;
|
||||
return (sample_weight >= CLOSURE_WEIGHT_CUTOFF) ? sc : NULL;
|
||||
memcpy((void *)sc, data, size);
|
||||
|
||||
sc->weight = weight;
|
||||
sc->sample_weight = sample_weight;
|
||||
|
||||
return sc;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -370,10 +370,13 @@ ccl_device void svm_node_tangent(KernelGlobals *kg, ShaderData *sd, float *stack
|
|||
|
||||
if (direction_type == NODE_TANGENT_UVMAP) {
|
||||
/* UV map */
|
||||
if (desc.offset == ATTR_STD_NOT_FOUND)
|
||||
tangent = make_float3(0.0f, 0.0f, 0.0f);
|
||||
else
|
||||
if (desc.offset == ATTR_STD_NOT_FOUND) {
|
||||
stack_store_float3(stack, tangent_offset, zero_float3());
|
||||
return;
|
||||
}
|
||||
else {
|
||||
tangent = attribute_value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* radial */
|
||||
|
|
|
@ -46,6 +46,12 @@ CCL_NAMESPACE_BEGIN
|
|||
|
||||
/* Geometry */
|
||||
|
||||
PackFlags operator|=(PackFlags &pack_flags, uint32_t value)
|
||||
{
|
||||
pack_flags = (PackFlags)((uint32_t)pack_flags | value);
|
||||
return pack_flags;
|
||||
}
|
||||
|
||||
NODE_ABSTRACT_DEFINE(Geometry)
|
||||
{
|
||||
NodeType *type = NodeType::add("geometry_base", NULL);
|
||||
|
@ -1236,7 +1242,16 @@ void GeometryManager::device_update_bvh(Device *device,
|
|||
|
||||
const bool can_refit = scene->bvh != nullptr &&
|
||||
(bparams.bvh_layout == BVHLayout::BVH_LAYOUT_OPTIX);
|
||||
const bool pack_all = scene->bvh == nullptr;
|
||||
|
||||
PackFlags pack_flags = PackFlags::PACK_NONE;
|
||||
|
||||
if (scene->bvh == nullptr) {
|
||||
pack_flags |= PackFlags::PACK_ALL;
|
||||
}
|
||||
|
||||
if (dscene->prim_visibility.is_modified()) {
|
||||
pack_flags |= PackFlags::PACK_VISIBILITY;
|
||||
}
|
||||
|
||||
BVH *bvh = scene->bvh;
|
||||
if (!scene->bvh) {
|
||||
|
@ -1274,10 +1289,14 @@ void GeometryManager::device_update_bvh(Device *device,
|
|||
|
||||
pack.root_index = -1;
|
||||
|
||||
if (!pack_all) {
|
||||
if (pack_flags != PackFlags::PACK_ALL) {
|
||||
/* if we do not need to recreate the BVH, then only the vertices are updated, so we can
|
||||
* safely retake the memory */
|
||||
dscene->prim_tri_verts.give_data(pack.prim_tri_verts);
|
||||
|
||||
if ((pack_flags & PackFlags::PACK_VISIBILITY) != 0) {
|
||||
dscene->prim_visibility.give_data(pack.prim_visibility);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* It is not strictly necessary to skip those resizes we if do not have to repack, as the OS
|
||||
|
@ -1306,13 +1325,21 @@ void GeometryManager::device_update_bvh(Device *device,
|
|||
// Iterate over scene mesh list instead of objects, since 'optix_prim_offset' was calculated
|
||||
// based on that list, which may be ordered differently from the object list.
|
||||
foreach (Geometry *geom, scene->geometry) {
|
||||
if (!pack_all && !geom->is_modified()) {
|
||||
/* Make a copy of the pack_flags so the current geometry's flags do not pollute the others'.
|
||||
*/
|
||||
PackFlags geom_pack_flags = pack_flags;
|
||||
|
||||
if (geom->is_modified()) {
|
||||
geom_pack_flags |= PackFlags::PACK_VERTICES;
|
||||
}
|
||||
|
||||
if (geom_pack_flags == PACK_NONE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const pair<int, uint> &info = geometry_to_object_info[geom];
|
||||
pool.push(function_bind(
|
||||
&Geometry::pack_primitives, geom, &pack, info.first, info.second, pack_all));
|
||||
&Geometry::pack_primitives, geom, &pack, info.first, info.second, geom_pack_flags));
|
||||
}
|
||||
pool.wait_work();
|
||||
}
|
||||
|
@ -1347,7 +1374,7 @@ void GeometryManager::device_update_bvh(Device *device,
|
|||
dscene->prim_type.steal_data(pack.prim_type);
|
||||
dscene->prim_type.copy_to_device();
|
||||
}
|
||||
if (pack.prim_visibility.size() && (dscene->prim_visibility.need_realloc() || has_bvh2_layout)) {
|
||||
if (pack.prim_visibility.size() && (dscene->prim_visibility.is_modified() || has_bvh2_layout)) {
|
||||
dscene->prim_visibility.steal_data(pack.prim_visibility);
|
||||
dscene->prim_visibility.copy_to_device();
|
||||
}
|
||||
|
@ -1595,6 +1622,10 @@ void GeometryManager::device_update_preprocess(Device *device, Scene *scene, Pro
|
|||
}
|
||||
}
|
||||
|
||||
if ((update_flags & VISIBILITY_MODIFIED) != 0) {
|
||||
dscene->prim_visibility.tag_modified();
|
||||
}
|
||||
|
||||
if (device_update_flags & ATTR_FLOAT_NEEDS_REALLOC) {
|
||||
dscene->attributes_map.tag_realloc();
|
||||
dscene->attributes_float.tag_realloc();
|
||||
|
@ -1921,7 +1952,8 @@ void GeometryManager::device_update(Device *device,
|
|||
* Also update the BVH if the transformations change, we cannot rely on tagging the Geometry
|
||||
* as modified in this case, as we may accumulate displacement if the vertices do not also
|
||||
* change. */
|
||||
bool need_update_scene_bvh = (scene->bvh == nullptr || (update_flags & TRANSFORM_MODIFIED) != 0);
|
||||
bool need_update_scene_bvh = (scene->bvh == nullptr ||
|
||||
(update_flags & (TRANSFORM_MODIFIED | VISIBILITY_MODIFIED)) != 0);
|
||||
{
|
||||
scoped_callback_timer timer([scene](double time) {
|
||||
if (scene->update_stats) {
|
||||
|
|
|
@ -43,6 +43,24 @@ class Shader;
|
|||
class Volume;
|
||||
struct PackedBVH;
|
||||
|
||||
/* Flags used to determine which geometry data need to be packed. */
|
||||
enum PackFlags : uint32_t {
|
||||
PACK_NONE = 0u,
|
||||
|
||||
/* Pack the geometry information (e.g. triangle or curve keys indices). */
|
||||
PACK_GEOMETRY = (1u << 0),
|
||||
|
||||
/* Pack the vertices, for Meshes and Volumes' bounding meshes. */
|
||||
PACK_VERTICES = (1u << 1),
|
||||
|
||||
/* Pack the visibility flags for each triangle or curve. */
|
||||
PACK_VISIBILITY = (1u << 2),
|
||||
|
||||
PACK_ALL = (PACK_GEOMETRY | PACK_VERTICES | PACK_VISIBILITY),
|
||||
};
|
||||
|
||||
PackFlags operator|=(PackFlags &pack_flags, uint32_t value);
|
||||
|
||||
/* Geometry
|
||||
*
|
||||
* Base class for geometric types like Mesh and Hair. */
|
||||
|
@ -126,7 +144,10 @@ class Geometry : public Node {
|
|||
int n,
|
||||
int total);
|
||||
|
||||
virtual void pack_primitives(PackedBVH *pack, int object, uint visibility, bool pack_all) = 0;
|
||||
virtual void pack_primitives(PackedBVH *pack,
|
||||
int object,
|
||||
uint visibility,
|
||||
PackFlags pack_flags) = 0;
|
||||
|
||||
/* Check whether the geometry should have own BVH built separately. Briefly,
|
||||
* own BVH is needed for geometry, if:
|
||||
|
@ -191,6 +212,8 @@ class GeometryManager {
|
|||
|
||||
TRANSFORM_MODIFIED = (1 << 10),
|
||||
|
||||
VISIBILITY_MODIFIED = (1 << 11),
|
||||
|
||||
/* tag everything in the manager for an update */
|
||||
UPDATE_ALL = ~0u,
|
||||
|
||||
|
|
|
@ -494,38 +494,47 @@ void Hair::pack_curves(Scene *scene,
|
|||
}
|
||||
}
|
||||
|
||||
void Hair::pack_primitives(PackedBVH *pack, int object, uint visibility, bool pack_all)
|
||||
void Hair::pack_primitives(PackedBVH *pack, int object, uint visibility, PackFlags pack_flags)
|
||||
{
|
||||
if (curve_first_key.empty())
|
||||
return;
|
||||
|
||||
/* If the BVH does not have to be recreated, we can bail out. */
|
||||
if (!pack_all) {
|
||||
return;
|
||||
/* Separate loop as other arrays are not initialized if their packing is not required. */
|
||||
if ((pack_flags & PACK_VISIBILITY) != 0) {
|
||||
unsigned int *prim_visibility = &pack->prim_visibility[optix_prim_offset];
|
||||
|
||||
size_t index = 0;
|
||||
for (size_t j = 0; j < num_curves(); ++j) {
|
||||
Curve curve = get_curve(j);
|
||||
for (size_t k = 0; k < curve.num_segments(); ++k, ++index) {
|
||||
prim_visibility[index] = visibility;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int *prim_tri_index = &pack->prim_tri_index[optix_prim_offset];
|
||||
int *prim_type = &pack->prim_type[optix_prim_offset];
|
||||
unsigned int *prim_visibility = &pack->prim_visibility[optix_prim_offset];
|
||||
int *prim_index = &pack->prim_index[optix_prim_offset];
|
||||
int *prim_object = &pack->prim_object[optix_prim_offset];
|
||||
// 'pack->prim_time' is unused by Embree and OptiX
|
||||
if ((pack_flags & PACK_GEOMETRY) != 0) {
|
||||
unsigned int *prim_tri_index = &pack->prim_tri_index[optix_prim_offset];
|
||||
int *prim_type = &pack->prim_type[optix_prim_offset];
|
||||
int *prim_index = &pack->prim_index[optix_prim_offset];
|
||||
int *prim_object = &pack->prim_object[optix_prim_offset];
|
||||
// 'pack->prim_time' is unused by Embree and OptiX
|
||||
|
||||
uint type = has_motion_blur() ?
|
||||
((curve_shape == CURVE_RIBBON) ? PRIMITIVE_MOTION_CURVE_RIBBON :
|
||||
PRIMITIVE_MOTION_CURVE_THICK) :
|
||||
((curve_shape == CURVE_RIBBON) ? PRIMITIVE_CURVE_RIBBON : PRIMITIVE_CURVE_THICK);
|
||||
uint type = has_motion_blur() ?
|
||||
((curve_shape == CURVE_RIBBON) ? PRIMITIVE_MOTION_CURVE_RIBBON :
|
||||
PRIMITIVE_MOTION_CURVE_THICK) :
|
||||
((curve_shape == CURVE_RIBBON) ? PRIMITIVE_CURVE_RIBBON :
|
||||
PRIMITIVE_CURVE_THICK);
|
||||
|
||||
size_t index = 0;
|
||||
for (size_t j = 0; j < num_curves(); ++j) {
|
||||
Curve curve = get_curve(j);
|
||||
for (size_t k = 0; k < curve.num_segments(); ++k, ++index) {
|
||||
prim_tri_index[index] = -1;
|
||||
prim_type[index] = PRIMITIVE_PACK_SEGMENT(type, k);
|
||||
prim_visibility[index] = visibility;
|
||||
// Each curve segment points back to its curve index
|
||||
prim_index[index] = j + prim_offset;
|
||||
prim_object[index] = object;
|
||||
size_t index = 0;
|
||||
for (size_t j = 0; j < num_curves(); ++j) {
|
||||
Curve curve = get_curve(j);
|
||||
for (size_t k = 0; k < curve.num_segments(); ++k, ++index) {
|
||||
prim_tri_index[index] = -1;
|
||||
prim_type[index] = PRIMITIVE_PACK_SEGMENT(type, k);
|
||||
// Each curve segment points back to its curve index
|
||||
prim_index[index] = j + prim_offset;
|
||||
prim_object[index] = object;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,7 +146,10 @@ class Hair : public Geometry {
|
|||
/* BVH */
|
||||
void pack_curves(Scene *scene, float4 *curve_key_co, float4 *curve_data, size_t curvekey_offset);
|
||||
|
||||
void pack_primitives(PackedBVH *pack, int object, uint visibility, bool pack_all) override;
|
||||
void pack_primitives(PackedBVH *pack,
|
||||
int object,
|
||||
uint visibility,
|
||||
PackFlags pack_flags) override;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
|
|
@ -805,7 +805,7 @@ void Mesh::pack_patches(uint *patch_data, uint vert_offset, uint face_offset, ui
|
|||
}
|
||||
}
|
||||
|
||||
void Mesh::pack_primitives(ccl::PackedBVH *pack, int object, uint visibility, bool pack_all)
|
||||
void Mesh::pack_primitives(ccl::PackedBVH *pack, int object, uint visibility, PackFlags pack_flags)
|
||||
{
|
||||
if (triangles.empty())
|
||||
return;
|
||||
|
@ -819,28 +819,38 @@ void Mesh::pack_primitives(ccl::PackedBVH *pack, int object, uint visibility, bo
|
|||
|
||||
uint type = has_motion_blur() ? PRIMITIVE_MOTION_TRIANGLE : PRIMITIVE_TRIANGLE;
|
||||
|
||||
if (pack_all) {
|
||||
/* Use optix_prim_offset for indexing as those arrays also contain data for Hair geometries. */
|
||||
unsigned int *prim_tri_index = &pack->prim_tri_index[optix_prim_offset];
|
||||
int *prim_type = &pack->prim_type[optix_prim_offset];
|
||||
/* Separate loop as other arrays are not initialized if their packing is not required. */
|
||||
if ((pack_flags & PackFlags::PACK_VISIBILITY) != 0) {
|
||||
unsigned int *prim_visibility = &pack->prim_visibility[optix_prim_offset];
|
||||
int *prim_index = &pack->prim_index[optix_prim_offset];
|
||||
int *prim_object = &pack->prim_object[optix_prim_offset];
|
||||
|
||||
for (size_t k = 0; k < num_prims; ++k) {
|
||||
prim_tri_index[k] = (prim_offset + k) * 3;
|
||||
prim_type[k] = type;
|
||||
prim_index[k] = prim_offset + k;
|
||||
prim_object[k] = object;
|
||||
prim_visibility[k] = visibility;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t k = 0; k < num_prims; ++k) {
|
||||
const Mesh::Triangle t = get_triangle(k);
|
||||
prim_tri_verts[k * 3] = float3_to_float4(verts[t.v[0]]);
|
||||
prim_tri_verts[k * 3 + 1] = float3_to_float4(verts[t.v[1]]);
|
||||
prim_tri_verts[k * 3 + 2] = float3_to_float4(verts[t.v[2]]);
|
||||
if ((pack_flags & PackFlags::PACK_GEOMETRY) != 0) {
|
||||
/* Use optix_prim_offset for indexing as those arrays also contain data for Hair geometries. */
|
||||
unsigned int *prim_tri_index = &pack->prim_tri_index[optix_prim_offset];
|
||||
int *prim_type = &pack->prim_type[optix_prim_offset];
|
||||
int *prim_index = &pack->prim_index[optix_prim_offset];
|
||||
int *prim_object = &pack->prim_object[optix_prim_offset];
|
||||
|
||||
for (size_t k = 0; k < num_prims; ++k) {
|
||||
if ((pack_flags & PackFlags::PACK_GEOMETRY) != 0) {
|
||||
prim_tri_index[k] = (prim_offset + k) * 3;
|
||||
prim_type[k] = type;
|
||||
prim_index[k] = prim_offset + k;
|
||||
prim_object[k] = object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((pack_flags & PackFlags::PACK_VERTICES) != 0) {
|
||||
for (size_t k = 0; k < num_prims; ++k) {
|
||||
const Mesh::Triangle t = get_triangle(k);
|
||||
prim_tri_verts[k * 3] = float3_to_float4(verts[t.v[0]]);
|
||||
prim_tri_verts[k * 3 + 1] = float3_to_float4(verts[t.v[1]]);
|
||||
prim_tri_verts[k * 3 + 2] = float3_to_float4(verts[t.v[2]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -232,7 +232,10 @@ class Mesh : public Geometry {
|
|||
size_t tri_offset);
|
||||
void pack_patches(uint *patch_data, uint vert_offset, uint face_offset, uint corner_offset);
|
||||
|
||||
void pack_primitives(PackedBVH *pack, int object, uint visibility, bool pack_all) override;
|
||||
void pack_primitives(PackedBVH *pack,
|
||||
int object,
|
||||
uint visibility,
|
||||
PackFlags pack_flags) override;
|
||||
|
||||
void tessellate(DiagSplit *split);
|
||||
|
||||
|
|
|
@ -220,6 +220,10 @@ void Object::tag_update(Scene *scene)
|
|||
flag |= ObjectManager::TRANSFORM_MODIFIED;
|
||||
}
|
||||
|
||||
if (visibility_is_modified()) {
|
||||
flag |= ObjectManager::VISIBILITY_MODIFIED;
|
||||
}
|
||||
|
||||
foreach (Node *node, geometry->get_used_shaders()) {
|
||||
Shader *shader = static_cast<Shader *>(node);
|
||||
if (shader->get_use_mis() && shader->has_surface_emission)
|
||||
|
@ -914,6 +918,10 @@ void ObjectManager::tag_update(Scene *scene, uint32_t flag)
|
|||
geometry_flag |= GeometryManager::TRANSFORM_MODIFIED;
|
||||
}
|
||||
|
||||
if ((flag & VISIBILITY_MODIFIED) != 0) {
|
||||
geometry_flag |= GeometryManager::VISIBILITY_MODIFIED;
|
||||
}
|
||||
|
||||
scene->geometry_manager->tag_update(scene, geometry_flag);
|
||||
}
|
||||
|
||||
|
|
|
@ -134,6 +134,7 @@ class ObjectManager {
|
|||
OBJECT_MODIFIED = (1 << 5),
|
||||
HOLDOUT_MODIFIED = (1 << 6),
|
||||
TRANSFORM_MODIFIED = (1 << 7),
|
||||
VISIBILITY_MODIFIED = (1 << 8),
|
||||
|
||||
/* tag everything in the manager for an update */
|
||||
UPDATE_ALL = ~0u,
|
||||
|
|
|
@ -72,9 +72,9 @@ class OSLShaderManager : public ShaderManager {
|
|||
|
||||
static void free_memory();
|
||||
|
||||
void reset(Scene *scene);
|
||||
void reset(Scene *scene) override;
|
||||
|
||||
bool use_osl()
|
||||
bool use_osl() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ class OSLShaderManager : public ShaderManager {
|
|||
DeviceScene *dscene,
|
||||
Scene *scene,
|
||||
Progress &progress) override;
|
||||
void device_free(Device *device, DeviceScene *dscene, Scene *scene);
|
||||
void device_free(Device *device, DeviceScene *dscene, Scene *scene) override;
|
||||
|
||||
/* osl compile and query */
|
||||
static bool osl_compile(const string &inputfile, const string &outputfile);
|
||||
|
|
|
@ -44,13 +44,13 @@ class SVMShaderManager : public ShaderManager {
|
|||
SVMShaderManager();
|
||||
~SVMShaderManager();
|
||||
|
||||
void reset(Scene *scene);
|
||||
void reset(Scene *scene) override;
|
||||
|
||||
void device_update_specific(Device *device,
|
||||
DeviceScene *dscene,
|
||||
Scene *scene,
|
||||
Progress &progress) override;
|
||||
void device_free(Device *device, DeviceScene *dscene, Scene *scene);
|
||||
void device_free(Device *device, DeviceScene *dscene, Scene *scene) override;
|
||||
|
||||
protected:
|
||||
void device_update_shader(Scene *scene,
|
||||
|
|
|
@ -79,6 +79,7 @@ set(SRC
|
|||
intern/GHOST_SystemPaths.h
|
||||
intern/GHOST_TimerManager.h
|
||||
intern/GHOST_TimerTask.h
|
||||
intern/GHOST_Util.h
|
||||
intern/GHOST_Window.h
|
||||
intern/GHOST_WindowManager.h
|
||||
)
|
||||
|
@ -438,6 +439,7 @@ endif()
|
|||
if(WITH_XR_OPENXR)
|
||||
list(APPEND SRC
|
||||
intern/GHOST_Xr.cpp
|
||||
intern/GHOST_XrAction.cpp
|
||||
intern/GHOST_XrContext.cpp
|
||||
intern/GHOST_XrEvent.cpp
|
||||
intern/GHOST_XrGraphicsBinding.cpp
|
||||
|
@ -446,6 +448,7 @@ if(WITH_XR_OPENXR)
|
|||
|
||||
GHOST_IXrContext.h
|
||||
intern/GHOST_IXrGraphicsBinding.h
|
||||
intern/GHOST_XrAction.h
|
||||
intern/GHOST_XrContext.h
|
||||
intern/GHOST_XrException.h
|
||||
intern/GHOST_XrSession.h
|
||||
|
|
|
@ -1059,7 +1059,110 @@ int GHOST_XrSessionNeedsUpsideDownDrawing(const GHOST_XrContextHandle xr_context
|
|||
* \returns GHOST_kSuccess if any event was handled, otherwise GHOST_kFailure.
|
||||
*/
|
||||
GHOST_TSuccess GHOST_XrEventsHandle(GHOST_XrContextHandle xr_context);
|
||||
#endif
|
||||
|
||||
/* actions */
|
||||
/**
|
||||
* Create an OpenXR action set for input/output.
|
||||
*/
|
||||
int GHOST_XrCreateActionSet(GHOST_XrContextHandle xr_context, const GHOST_XrActionSetInfo *info);
|
||||
|
||||
/**
|
||||
* Destroy a previously created OpenXR action set.
|
||||
*/
|
||||
void GHOST_XrDestroyActionSet(GHOST_XrContextHandle xr_context, const char *action_set_name);
|
||||
|
||||
/**
|
||||
* Create OpenXR input/output actions.
|
||||
*/
|
||||
int GHOST_XrCreateActions(GHOST_XrContextHandle xr_context,
|
||||
const char *action_set_name,
|
||||
GHOST_TUns32 count,
|
||||
const GHOST_XrActionInfo *infos);
|
||||
|
||||
/**
|
||||
* Destroy previously created OpenXR actions.
|
||||
*/
|
||||
void GHOST_XrDestroyActions(GHOST_XrContextHandle xr_context,
|
||||
const char *action_set_name,
|
||||
GHOST_TUns32 count,
|
||||
const char *const *action_names);
|
||||
|
||||
/**
|
||||
* Create spaces for pose-based OpenXR actions.
|
||||
*/
|
||||
int GHOST_XrCreateActionSpaces(GHOST_XrContextHandle xr_context,
|
||||
const char *action_set_name,
|
||||
GHOST_TUns32 count,
|
||||
const GHOST_XrActionSpaceInfo *infos);
|
||||
|
||||
/**
|
||||
* Destroy previously created spaces for OpenXR actions.
|
||||
*/
|
||||
void GHOST_XrDestroyActionSpaces(GHOST_XrContextHandle xr_context,
|
||||
const char *action_set_name,
|
||||
GHOST_TUns32 count,
|
||||
const GHOST_XrActionSpaceInfo *infos);
|
||||
|
||||
/**
|
||||
* Create input/output path bindings for OpenXR actions.
|
||||
*/
|
||||
int GHOST_XrCreateActionBindings(GHOST_XrContextHandle xr_context,
|
||||
const char *action_set_name,
|
||||
GHOST_TUns32 count,
|
||||
const GHOST_XrActionProfileInfo *infos);
|
||||
|
||||
/**
|
||||
* Destroy previously created bindings for OpenXR actions.
|
||||
*/
|
||||
void GHOST_XrDestroyActionBindings(GHOST_XrContextHandle xr_context,
|
||||
const char *action_set_name,
|
||||
GHOST_TUns32 count,
|
||||
const GHOST_XrActionProfileInfo *infos);
|
||||
|
||||
/**
|
||||
* Attach all created action sets to the current OpenXR session.
|
||||
*/
|
||||
int GHOST_XrAttachActionSets(GHOST_XrContextHandle xr_context);
|
||||
|
||||
/**
|
||||
* Update button/tracking states for OpenXR actions.
|
||||
*
|
||||
* \param action_set_name: The name of the action set to sync. If NULL, all action sets
|
||||
* attached to the session will be synced.
|
||||
*/
|
||||
int GHOST_XrSyncActions(GHOST_XrContextHandle xr_context, const char *action_set_name);
|
||||
|
||||
/**
|
||||
* Apply an OpenXR haptic output action.
|
||||
*/
|
||||
int GHOST_XrApplyHapticAction(GHOST_XrContextHandle xr_context,
|
||||
const char *action_set_name,
|
||||
const char *action_name,
|
||||
const GHOST_TInt64 *duration,
|
||||
const float *frequency,
|
||||
const float *amplitude);
|
||||
|
||||
/**
|
||||
* Stop a previously applied OpenXR haptic output action.
|
||||
*/
|
||||
void GHOST_XrStopHapticAction(GHOST_XrContextHandle xr_context,
|
||||
const char *action_set_name,
|
||||
const char *action_name);
|
||||
|
||||
/**
|
||||
* Get action set custom data (owned by Blender, not GHOST).
|
||||
*/
|
||||
void *GHOST_XrGetActionSetCustomdata(GHOST_XrContextHandle xr_context,
|
||||
const char *action_set_name);
|
||||
|
||||
/**
|
||||
* Get action custom data (owned by Blender, not GHOST).
|
||||
*/
|
||||
void *GHOST_XrGetActionCustomdata(GHOST_XrContextHandle xr_context,
|
||||
const char *action_set_name,
|
||||
const char *action_name);
|
||||
|
||||
#endif /* WITH_XR_OPENXR */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
|
||||
#include "GHOST_Types.h"
|
||||
|
||||
class GHOST_XrSession;
|
||||
|
||||
class GHOST_IXrContext {
|
||||
public:
|
||||
virtual ~GHOST_IXrContext() = default;
|
||||
|
@ -31,6 +33,10 @@ class GHOST_IXrContext {
|
|||
virtual bool isSessionRunning() const = 0;
|
||||
virtual void drawSessionViews(void *draw_customdata) = 0;
|
||||
|
||||
/* Needed for the GHOST C api. */
|
||||
virtual GHOST_XrSession *getSession() = 0;
|
||||
virtual const GHOST_XrSession *getSession() const = 0;
|
||||
|
||||
virtual void dispatchErrorMessage(const class GHOST_XrException *) const = 0;
|
||||
|
||||
virtual void setGraphicsContextBindFuncs(GHOST_XrGraphicsContextBindFn bind_fn,
|
||||
|
|
|
@ -634,7 +634,9 @@ typedef enum GHOST_TXrGraphicsBinding {
|
|||
|
||||
typedef void (*GHOST_XrErrorHandlerFn)(const struct GHOST_XrError *);
|
||||
|
||||
typedef void (*GHOST_XrSessionCreateFn)(void);
|
||||
typedef void (*GHOST_XrSessionExitFn)(void *customdata);
|
||||
typedef void (*GHOST_XrCustomdataFreeFn)(void *customdata);
|
||||
|
||||
typedef void *(*GHOST_XrGraphicsContextBindFn)(void);
|
||||
typedef void (*GHOST_XrGraphicsContextUnbindFn)(GHOST_ContextHandle graphics_context);
|
||||
|
@ -665,6 +667,7 @@ typedef struct {
|
|||
typedef struct {
|
||||
GHOST_XrPose base_pose;
|
||||
|
||||
GHOST_XrSessionCreateFn create_fn;
|
||||
GHOST_XrSessionExitFn exit_fn;
|
||||
void *exit_customdata;
|
||||
} GHOST_XrSessionBeginInfo;
|
||||
|
@ -691,4 +694,54 @@ typedef struct GHOST_XrError {
|
|||
void *customdata;
|
||||
} GHOST_XrError;
|
||||
|
||||
#endif
|
||||
typedef struct GHOST_XrActionSetInfo {
|
||||
const char *name;
|
||||
|
||||
GHOST_XrCustomdataFreeFn customdata_free_fn;
|
||||
void *customdata; /* wmXrActionSet */
|
||||
} GHOST_XrActionSetInfo;
|
||||
|
||||
/** XR action type. Enum values match those in OpenXR's
|
||||
* XrActionType enum for consistency. */
|
||||
typedef enum GHOST_XrActionType {
|
||||
GHOST_kXrActionTypeBooleanInput = 1,
|
||||
GHOST_kXrActionTypeFloatInput = 2,
|
||||
GHOST_kXrActionTypeVector2fInput = 3,
|
||||
GHOST_kXrActionTypePoseInput = 4,
|
||||
GHOST_kXrActionTypeVibrationOutput = 100,
|
||||
} GHOST_XrActionType;
|
||||
|
||||
typedef struct GHOST_XrActionInfo {
|
||||
const char *name;
|
||||
GHOST_XrActionType type;
|
||||
GHOST_TUns32 count_subaction_paths;
|
||||
const char **subaction_paths;
|
||||
/** States for each subaction path. */
|
||||
void *states;
|
||||
|
||||
GHOST_XrCustomdataFreeFn customdata_free_fn;
|
||||
void *customdata; /* wmXrAction */
|
||||
} GHOST_XrActionInfo;
|
||||
|
||||
typedef struct GHOST_XrActionSpaceInfo {
|
||||
const char *action_name;
|
||||
GHOST_TUns32 count_subaction_paths;
|
||||
const char **subaction_paths;
|
||||
/** Poses for each subaction path. */
|
||||
const GHOST_XrPose *poses;
|
||||
} GHOST_XrActionSpaceInfo;
|
||||
|
||||
typedef struct GHOST_XrActionBindingInfo {
|
||||
const char *action_name;
|
||||
GHOST_TUns32 count_interaction_paths;
|
||||
/** Interaction path: User (subaction) path + component path. */
|
||||
const char **interaction_paths;
|
||||
} GHOST_XrActionBindingInfo;
|
||||
|
||||
typedef struct GHOST_XrActionProfileInfo {
|
||||
const char *profile_path;
|
||||
GHOST_TUns32 count_bindings;
|
||||
const GHOST_XrActionBindingInfo *bindings;
|
||||
} GHOST_XrActionProfileInfo;
|
||||
|
||||
#endif /* WITH_XR_OPENXR */
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include "intern/GHOST_Debug.h"
|
||||
#ifdef WITH_XR_OPENXR
|
||||
# include "GHOST_IXrContext.h"
|
||||
# include "intern/GHOST_XrSession.h"
|
||||
#endif
|
||||
#include "intern/GHOST_CallbackEventConsumer.h"
|
||||
#include "intern/GHOST_XrException.h"
|
||||
|
@ -953,4 +954,145 @@ int GHOST_XrSessionNeedsUpsideDownDrawing(const GHOST_XrContextHandle xr_context
|
|||
return 0; /* Only reached if exception is thrown. */
|
||||
}
|
||||
|
||||
#endif
|
||||
int GHOST_XrCreateActionSet(GHOST_XrContextHandle xr_contexthandle,
|
||||
const GHOST_XrActionSetInfo *info)
|
||||
{
|
||||
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
|
||||
GHOST_XrSession *xr_session = xr_context->getSession();
|
||||
GHOST_XR_CAPI_CALL_RET(xr_session->createActionSet(*info), xr_context);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GHOST_XrDestroyActionSet(GHOST_XrContextHandle xr_contexthandle, const char *action_set_name)
|
||||
{
|
||||
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
|
||||
GHOST_XrSession *xr_session = xr_context->getSession();
|
||||
GHOST_XR_CAPI_CALL(xr_session->destroyActionSet(action_set_name), xr_context);
|
||||
}
|
||||
|
||||
int GHOST_XrCreateActions(GHOST_XrContextHandle xr_contexthandle,
|
||||
const char *action_set_name,
|
||||
GHOST_TUns32 count,
|
||||
const GHOST_XrActionInfo *infos)
|
||||
{
|
||||
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
|
||||
GHOST_XrSession *xr_session = xr_context->getSession();
|
||||
GHOST_XR_CAPI_CALL_RET(xr_session->createActions(action_set_name, count, infos), xr_context);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GHOST_XrDestroyActions(GHOST_XrContextHandle xr_contexthandle,
|
||||
const char *action_set_name,
|
||||
GHOST_TUns32 count,
|
||||
const char *const *action_names)
|
||||
{
|
||||
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
|
||||
GHOST_XrSession *xr_session = xr_context->getSession();
|
||||
GHOST_XR_CAPI_CALL(xr_session->destroyActions(action_set_name, count, action_names), xr_context);
|
||||
}
|
||||
|
||||
int GHOST_XrCreateActionSpaces(GHOST_XrContextHandle xr_contexthandle,
|
||||
const char *action_set_name,
|
||||
GHOST_TUns32 count,
|
||||
const GHOST_XrActionSpaceInfo *infos)
|
||||
{
|
||||
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
|
||||
GHOST_XrSession *xr_session = xr_context->getSession();
|
||||
GHOST_XR_CAPI_CALL_RET(xr_session->createActionSpaces(action_set_name, count, infos),
|
||||
xr_context);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GHOST_XrDestroyActionSpaces(GHOST_XrContextHandle xr_contexthandle,
|
||||
const char *action_set_name,
|
||||
GHOST_TUns32 count,
|
||||
const GHOST_XrActionSpaceInfo *infos)
|
||||
{
|
||||
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
|
||||
GHOST_XrSession *xr_session = xr_context->getSession();
|
||||
GHOST_XR_CAPI_CALL(xr_session->destroyActionSpaces(action_set_name, count, infos), xr_context);
|
||||
}
|
||||
|
||||
int GHOST_XrCreateActionBindings(GHOST_XrContextHandle xr_contexthandle,
|
||||
const char *action_set_name,
|
||||
GHOST_TUns32 count,
|
||||
const GHOST_XrActionProfileInfo *infos)
|
||||
{
|
||||
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
|
||||
GHOST_XrSession *xr_session = xr_context->getSession();
|
||||
GHOST_XR_CAPI_CALL_RET(xr_session->createActionBindings(action_set_name, count, infos),
|
||||
xr_context);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GHOST_XrDestroyActionBindings(GHOST_XrContextHandle xr_contexthandle,
|
||||
const char *action_set_name,
|
||||
GHOST_TUns32 count,
|
||||
const GHOST_XrActionProfileInfo *infos)
|
||||
{
|
||||
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
|
||||
GHOST_XrSession *xr_session = xr_context->getSession();
|
||||
GHOST_XR_CAPI_CALL(xr_session->destroyActionBindings(action_set_name, count, infos), xr_context);
|
||||
}
|
||||
|
||||
int GHOST_XrAttachActionSets(GHOST_XrContextHandle xr_contexthandle)
|
||||
{
|
||||
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
|
||||
GHOST_XrSession *xr_session = xr_context->getSession();
|
||||
GHOST_XR_CAPI_CALL_RET(xr_session->attachActionSets(), xr_context);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int GHOST_XrSyncActions(GHOST_XrContextHandle xr_contexthandle, const char *action_set_name)
|
||||
{
|
||||
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
|
||||
GHOST_XrSession *xr_session = xr_context->getSession();
|
||||
GHOST_XR_CAPI_CALL_RET(xr_session->syncActions(action_set_name), xr_context);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int GHOST_XrApplyHapticAction(GHOST_XrContextHandle xr_contexthandle,
|
||||
const char *action_set_name,
|
||||
const char *action_name,
|
||||
const GHOST_TInt64 *duration,
|
||||
const float *frequency,
|
||||
const float *amplitude)
|
||||
{
|
||||
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
|
||||
GHOST_XrSession *xr_session = xr_context->getSession();
|
||||
GHOST_XR_CAPI_CALL_RET(xr_session->applyHapticAction(
|
||||
action_set_name, action_name, *duration, *frequency, *amplitude),
|
||||
xr_context);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GHOST_XrStopHapticAction(GHOST_XrContextHandle xr_contexthandle,
|
||||
const char *action_set_name,
|
||||
const char *action_name)
|
||||
{
|
||||
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
|
||||
GHOST_XrSession *xr_session = xr_context->getSession();
|
||||
GHOST_XR_CAPI_CALL(xr_session->stopHapticAction(action_set_name, action_name), xr_context);
|
||||
}
|
||||
|
||||
void *GHOST_XrGetActionSetCustomdata(GHOST_XrContextHandle xr_contexthandle,
|
||||
const char *action_set_name)
|
||||
{
|
||||
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
|
||||
GHOST_XrSession *xr_session = xr_context->getSession();
|
||||
GHOST_XR_CAPI_CALL_RET(xr_session->getActionSetCustomdata(action_set_name), xr_context);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *GHOST_XrGetActionCustomdata(GHOST_XrContextHandle xr_contexthandle,
|
||||
const char *action_set_name,
|
||||
const char *action_name)
|
||||
{
|
||||
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
|
||||
GHOST_XrSession *xr_session = xr_context->getSession();
|
||||
GHOST_XR_CAPI_CALL_RET(xr_session->getActionCustomdata(action_set_name, action_name),
|
||||
xr_context);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* WITH_XR_OPENXR */
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup GHOST
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
/**
|
||||
* RAII wrapper for typical C `void *` custom data.
|
||||
* Used for exception safe custom-data handling during constructor calls.
|
||||
*/
|
||||
struct GHOST_C_CustomDataWrapper {
|
||||
using FreeFn = std::function<void(void *)>;
|
||||
|
||||
void *custom_data_;
|
||||
FreeFn free_fn_;
|
||||
|
||||
GHOST_C_CustomDataWrapper(void *custom_data, FreeFn free_fn)
|
||||
: custom_data_(custom_data), free_fn_(free_fn)
|
||||
{
|
||||
}
|
||||
~GHOST_C_CustomDataWrapper()
|
||||
{
|
||||
if (free_fn_ != nullptr && custom_data_ != nullptr) {
|
||||
free_fn_(custom_data_);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,477 @@
|
|||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup GHOST
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
#include "GHOST_Types.h"
|
||||
|
||||
#include "GHOST_XrException.h"
|
||||
#include "GHOST_Xr_intern.h"
|
||||
|
||||
#include "GHOST_XrAction.h"
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name GHOST_XrActionSpace
|
||||
*
|
||||
* \{ */
|
||||
|
||||
GHOST_XrActionSpace::GHOST_XrActionSpace(XrInstance instance,
|
||||
XrSession session,
|
||||
XrAction action,
|
||||
const GHOST_XrActionSpaceInfo &info,
|
||||
uint32_t subaction_idx)
|
||||
{
|
||||
const char *subaction_path = info.subaction_paths[subaction_idx];
|
||||
CHECK_XR(xrStringToPath(instance, subaction_path, &m_subaction_path),
|
||||
(std::string("Failed to get user path \"") + subaction_path + "\".").data());
|
||||
|
||||
XrActionSpaceCreateInfo action_space_info{XR_TYPE_ACTION_SPACE_CREATE_INFO};
|
||||
action_space_info.action = action;
|
||||
action_space_info.subactionPath = m_subaction_path;
|
||||
copy_ghost_pose_to_openxr_pose(info.poses[subaction_idx], action_space_info.poseInActionSpace);
|
||||
|
||||
CHECK_XR(xrCreateActionSpace(session, &action_space_info, &m_space),
|
||||
(std::string("Failed to create space \"") + subaction_path + "\" for action \"" +
|
||||
info.action_name + "\".")
|
||||
.data());
|
||||
}
|
||||
|
||||
GHOST_XrActionSpace::~GHOST_XrActionSpace()
|
||||
{
|
||||
if (m_space != XR_NULL_HANDLE) {
|
||||
CHECK_XR_ASSERT(xrDestroySpace(m_space));
|
||||
}
|
||||
}
|
||||
|
||||
XrSpace GHOST_XrActionSpace::getSpace() const
|
||||
{
|
||||
return m_space;
|
||||
}
|
||||
|
||||
const XrPath &GHOST_XrActionSpace::getSubactionPath() const
|
||||
{
|
||||
return m_subaction_path;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name GHOST_XrActionProfile
|
||||
*
|
||||
* \{ */
|
||||
|
||||
GHOST_XrActionProfile::GHOST_XrActionProfile(XrInstance instance,
|
||||
XrAction action,
|
||||
const char *profile_path,
|
||||
const GHOST_XrActionBindingInfo &info)
|
||||
{
|
||||
CHECK_XR(
|
||||
xrStringToPath(instance, profile_path, &m_profile),
|
||||
(std::string("Failed to get interaction profile path \"") + profile_path + "\".").data());
|
||||
|
||||
/* Create bindings. */
|
||||
XrInteractionProfileSuggestedBinding bindings_info{
|
||||
XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING};
|
||||
bindings_info.interactionProfile = m_profile;
|
||||
bindings_info.countSuggestedBindings = 1;
|
||||
|
||||
for (uint32_t interaction_idx = 0; interaction_idx < info.count_interaction_paths;
|
||||
++interaction_idx) {
|
||||
const char *interaction_path = info.interaction_paths[interaction_idx];
|
||||
if (m_bindings.find(interaction_path) != m_bindings.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
XrActionSuggestedBinding sbinding;
|
||||
sbinding.action = action;
|
||||
CHECK_XR(xrStringToPath(instance, interaction_path, &sbinding.binding),
|
||||
(std::string("Failed to get interaction path \"") + interaction_path + "\".").data());
|
||||
bindings_info.suggestedBindings = &sbinding;
|
||||
|
||||
/* Although the bindings will be re-suggested in GHOST_XrSession::attachActionSets(), it
|
||||
* greatly improves error checking to suggest them here first. */
|
||||
CHECK_XR(xrSuggestInteractionProfileBindings(instance, &bindings_info),
|
||||
(std::string("Failed to create binding for profile \"") + profile_path +
|
||||
"\" and action \"" + info.action_name +
|
||||
"\". Are the profile and action paths correct?")
|
||||
.data());
|
||||
|
||||
m_bindings.insert({interaction_path, sbinding.binding});
|
||||
}
|
||||
}
|
||||
|
||||
void GHOST_XrActionProfile::getBindings(
|
||||
XrAction action, std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const
|
||||
{
|
||||
std::map<XrPath, std::vector<XrActionSuggestedBinding>>::iterator it = r_bindings.find(
|
||||
m_profile);
|
||||
if (it == r_bindings.end()) {
|
||||
it = r_bindings
|
||||
.emplace(std::piecewise_construct, std::make_tuple(m_profile), std::make_tuple())
|
||||
.first;
|
||||
}
|
||||
|
||||
std::vector<XrActionSuggestedBinding> &sbindings = it->second;
|
||||
|
||||
for (auto &[path, binding] : m_bindings) {
|
||||
XrActionSuggestedBinding sbinding;
|
||||
sbinding.action = action;
|
||||
sbinding.binding = binding;
|
||||
|
||||
sbindings.push_back(std::move(sbinding));
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name GHOST_XrAction
|
||||
*
|
||||
* \{ */
|
||||
|
||||
GHOST_XrAction::GHOST_XrAction(XrInstance instance,
|
||||
XrActionSet action_set,
|
||||
const GHOST_XrActionInfo &info)
|
||||
: m_type(info.type),
|
||||
m_states(info.states),
|
||||
m_custom_data_(
|
||||
std::make_unique<GHOST_C_CustomDataWrapper>(info.customdata, info.customdata_free_fn))
|
||||
{
|
||||
m_subaction_paths.resize(info.count_subaction_paths);
|
||||
|
||||
for (uint32_t i = 0; i < info.count_subaction_paths; ++i) {
|
||||
CHECK_XR(xrStringToPath(instance, info.subaction_paths[i], &m_subaction_paths[i]),
|
||||
(std::string("Failed to get user path \"") + info.subaction_paths[i] + "\".").data());
|
||||
}
|
||||
|
||||
XrActionCreateInfo action_info{XR_TYPE_ACTION_CREATE_INFO};
|
||||
strcpy(action_info.actionName, info.name);
|
||||
strcpy(action_info.localizedActionName, info.name); /* Just use same name for localized. This can
|
||||
be changed in the future if necessary. */
|
||||
|
||||
switch (info.type) {
|
||||
case GHOST_kXrActionTypeBooleanInput:
|
||||
action_info.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
|
||||
break;
|
||||
case GHOST_kXrActionTypeFloatInput:
|
||||
action_info.actionType = XR_ACTION_TYPE_FLOAT_INPUT;
|
||||
break;
|
||||
case GHOST_kXrActionTypeVector2fInput:
|
||||
action_info.actionType = XR_ACTION_TYPE_VECTOR2F_INPUT;
|
||||
break;
|
||||
case GHOST_kXrActionTypePoseInput:
|
||||
action_info.actionType = XR_ACTION_TYPE_POSE_INPUT;
|
||||
break;
|
||||
case GHOST_kXrActionTypeVibrationOutput:
|
||||
action_info.actionType = XR_ACTION_TYPE_VIBRATION_OUTPUT;
|
||||
break;
|
||||
}
|
||||
action_info.countSubactionPaths = info.count_subaction_paths;
|
||||
action_info.subactionPaths = m_subaction_paths.data();
|
||||
|
||||
CHECK_XR(xrCreateAction(action_set, &action_info, &m_action),
|
||||
(std::string("Failed to create action \"") + info.name +
|
||||
"\". Action name and/or paths are invalid. Name must not contain upper "
|
||||
"case letters or special characters other than '-', '_', or '.'.")
|
||||
.data());
|
||||
}
|
||||
|
||||
GHOST_XrAction::~GHOST_XrAction()
|
||||
{
|
||||
if (m_action != XR_NULL_HANDLE) {
|
||||
CHECK_XR_ASSERT(xrDestroyAction(m_action));
|
||||
}
|
||||
}
|
||||
|
||||
bool GHOST_XrAction::createSpace(XrInstance instance,
|
||||
XrSession session,
|
||||
const GHOST_XrActionSpaceInfo &info)
|
||||
{
|
||||
uint32_t subaction_idx = 0;
|
||||
for (; subaction_idx < info.count_subaction_paths; ++subaction_idx) {
|
||||
if (m_spaces.find(info.subaction_paths[subaction_idx]) != m_spaces.end()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (subaction_idx = 0; subaction_idx < info.count_subaction_paths; ++subaction_idx) {
|
||||
m_spaces.emplace(std::piecewise_construct,
|
||||
std::make_tuple(info.subaction_paths[subaction_idx]),
|
||||
std::make_tuple(instance, session, m_action, info, subaction_idx));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GHOST_XrAction::destroySpace(const char *subaction_path)
|
||||
{
|
||||
if (m_spaces.find(subaction_path) != m_spaces.end()) {
|
||||
m_spaces.erase(subaction_path);
|
||||
}
|
||||
}
|
||||
|
||||
bool GHOST_XrAction::createBinding(XrInstance instance,
|
||||
const char *profile_path,
|
||||
const GHOST_XrActionBindingInfo &info)
|
||||
{
|
||||
if (m_profiles.find(profile_path) != m_profiles.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_profiles.emplace(std::piecewise_construct,
|
||||
std::make_tuple(profile_path),
|
||||
std::make_tuple(instance, m_action, profile_path, info));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GHOST_XrAction::destroyBinding(const char *interaction_profile_path)
|
||||
{
|
||||
if (m_profiles.find(interaction_profile_path) != m_profiles.end()) {
|
||||
m_profiles.erase(interaction_profile_path);
|
||||
}
|
||||
}
|
||||
|
||||
void GHOST_XrAction::updateState(XrSession session,
|
||||
const char *action_name,
|
||||
XrSpace reference_space,
|
||||
const XrTime &predicted_display_time)
|
||||
{
|
||||
XrActionStateGetInfo state_info{XR_TYPE_ACTION_STATE_GET_INFO};
|
||||
state_info.action = m_action;
|
||||
|
||||
const size_t count_subaction_paths = m_subaction_paths.size();
|
||||
for (size_t subaction_idx = 0; subaction_idx < count_subaction_paths; ++subaction_idx) {
|
||||
state_info.subactionPath = m_subaction_paths[subaction_idx];
|
||||
|
||||
switch (m_type) {
|
||||
case GHOST_kXrActionTypeBooleanInput: {
|
||||
XrActionStateBoolean state{XR_TYPE_ACTION_STATE_BOOLEAN};
|
||||
CHECK_XR(xrGetActionStateBoolean(session, &state_info, &state),
|
||||
(std::string("Failed to get state for boolean action \"") + action_name + "\".")
|
||||
.data());
|
||||
if (state.isActive) {
|
||||
((bool *)m_states)[subaction_idx] = state.currentState;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GHOST_kXrActionTypeFloatInput: {
|
||||
XrActionStateFloat state{XR_TYPE_ACTION_STATE_FLOAT};
|
||||
CHECK_XR(
|
||||
xrGetActionStateFloat(session, &state_info, &state),
|
||||
(std::string("Failed to get state for float action \"") + action_name + "\".").data());
|
||||
if (state.isActive) {
|
||||
((float *)m_states)[subaction_idx] = state.currentState;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GHOST_kXrActionTypeVector2fInput: {
|
||||
XrActionStateVector2f state{XR_TYPE_ACTION_STATE_VECTOR2F};
|
||||
CHECK_XR(xrGetActionStateVector2f(session, &state_info, &state),
|
||||
(std::string("Failed to get state for vector2f action \"") + action_name + "\".")
|
||||
.data());
|
||||
if (state.isActive) {
|
||||
memcpy(((float(*)[2])m_states)[subaction_idx], &state.currentState, sizeof(float[2]));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GHOST_kXrActionTypePoseInput: {
|
||||
XrActionStatePose state{XR_TYPE_ACTION_STATE_POSE};
|
||||
CHECK_XR(
|
||||
xrGetActionStatePose(session, &state_info, &state),
|
||||
(std::string("Failed to get state for pose action \"") + action_name + "\".").data());
|
||||
if (state.isActive) {
|
||||
XrSpace pose_space = XR_NULL_HANDLE;
|
||||
for (auto &[path, space] : m_spaces) {
|
||||
if (space.getSubactionPath() == state_info.subactionPath) {
|
||||
pose_space = space.getSpace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pose_space != XR_NULL_HANDLE) {
|
||||
XrSpaceLocation space_location{XR_TYPE_SPACE_LOCATION};
|
||||
CHECK_XR(
|
||||
xrLocateSpace(
|
||||
pose_space, reference_space, predicted_display_time, &space_location),
|
||||
(std::string("Failed to query pose space for action \"") + action_name + "\".")
|
||||
.data());
|
||||
copy_openxr_pose_to_ghost_pose(space_location.pose,
|
||||
((GHOST_XrPose *)m_states)[subaction_idx]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GHOST_kXrActionTypeVibrationOutput: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GHOST_XrAction::applyHapticFeedback(XrSession session,
|
||||
const char *action_name,
|
||||
const GHOST_TInt64 &duration,
|
||||
const float &frequency,
|
||||
const float &litude)
|
||||
{
|
||||
XrHapticVibration vibration{XR_TYPE_HAPTIC_VIBRATION};
|
||||
vibration.duration = (duration == 0) ? XR_MIN_HAPTIC_DURATION :
|
||||
static_cast<XrDuration>(duration);
|
||||
vibration.frequency = frequency;
|
||||
vibration.amplitude = amplitude;
|
||||
|
||||
XrHapticActionInfo haptic_info{XR_TYPE_HAPTIC_ACTION_INFO};
|
||||
haptic_info.action = m_action;
|
||||
|
||||
for (std::vector<XrPath>::iterator it = m_subaction_paths.begin(); it != m_subaction_paths.end();
|
||||
++it) {
|
||||
haptic_info.subactionPath = *it;
|
||||
CHECK_XR(xrApplyHapticFeedback(session, &haptic_info, (const XrHapticBaseHeader *)&vibration),
|
||||
(std::string("Failed to apply haptic action \"") + action_name + "\".").data());
|
||||
}
|
||||
}
|
||||
|
||||
void GHOST_XrAction::stopHapticFeedback(XrSession session, const char *action_name)
|
||||
{
|
||||
XrHapticActionInfo haptic_info{XR_TYPE_HAPTIC_ACTION_INFO};
|
||||
haptic_info.action = m_action;
|
||||
|
||||
for (std::vector<XrPath>::iterator it = m_subaction_paths.begin(); it != m_subaction_paths.end();
|
||||
++it) {
|
||||
haptic_info.subactionPath = *it;
|
||||
CHECK_XR(xrStopHapticFeedback(session, &haptic_info),
|
||||
(std::string("Failed to stop haptic action \"") + action_name + "\".").data());
|
||||
}
|
||||
}
|
||||
|
||||
void *GHOST_XrAction::getCustomdata()
|
||||
{
|
||||
if (m_custom_data_ == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return m_custom_data_->custom_data_;
|
||||
}
|
||||
|
||||
void GHOST_XrAction::getBindings(
|
||||
std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const
|
||||
{
|
||||
for (auto &[path, profile] : m_profiles) {
|
||||
profile.getBindings(m_action, r_bindings);
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name GHOST_XrActionSet
|
||||
*
|
||||
* \{ */
|
||||
|
||||
GHOST_XrActionSet::GHOST_XrActionSet(XrInstance instance, const GHOST_XrActionSetInfo &info)
|
||||
: m_custom_data_(
|
||||
std::make_unique<GHOST_C_CustomDataWrapper>(info.customdata, info.customdata_free_fn))
|
||||
{
|
||||
XrActionSetCreateInfo action_set_info{XR_TYPE_ACTION_SET_CREATE_INFO};
|
||||
strcpy(action_set_info.actionSetName, info.name);
|
||||
strcpy(action_set_info.localizedActionSetName,
|
||||
info.name); /* Just use same name for localized. This can be changed in the future if
|
||||
necessary. */
|
||||
action_set_info.priority = 0; /* Use same (default) priority for all action sets. */
|
||||
|
||||
CHECK_XR(xrCreateActionSet(instance, &action_set_info, &m_action_set),
|
||||
(std::string("Failed to create action set \"") + info.name +
|
||||
"\". Name must not contain upper case letters or special characters "
|
||||
"other than '-', '_', or '.'.")
|
||||
.data());
|
||||
}
|
||||
|
||||
GHOST_XrActionSet::~GHOST_XrActionSet()
|
||||
{
|
||||
/* This needs to be done before xrDestroyActionSet() to avoid an assertion in the GHOST_XrAction
|
||||
* destructor (which calls xrDestroyAction()). */
|
||||
m_actions.clear();
|
||||
|
||||
if (m_action_set != XR_NULL_HANDLE) {
|
||||
CHECK_XR_ASSERT(xrDestroyActionSet(m_action_set));
|
||||
}
|
||||
}
|
||||
|
||||
bool GHOST_XrActionSet::createAction(XrInstance instance, const GHOST_XrActionInfo &info)
|
||||
{
|
||||
if (m_actions.find(info.name) != m_actions.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_actions.emplace(std::piecewise_construct,
|
||||
std::make_tuple(info.name),
|
||||
std::make_tuple(instance, m_action_set, info));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GHOST_XrActionSet::destroyAction(const char *action_name)
|
||||
{
|
||||
if (m_actions.find(action_name) != m_actions.end()) {
|
||||
m_actions.erase(action_name);
|
||||
}
|
||||
}
|
||||
|
||||
GHOST_XrAction *GHOST_XrActionSet::findAction(const char *action_name)
|
||||
{
|
||||
std::map<std::string, GHOST_XrAction>::iterator it = m_actions.find(action_name);
|
||||
if (it == m_actions.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
void GHOST_XrActionSet::updateStates(XrSession session,
|
||||
XrSpace reference_space,
|
||||
const XrTime &predicted_display_time)
|
||||
{
|
||||
for (auto &[name, action] : m_actions) {
|
||||
action.updateState(session, name.data(), reference_space, predicted_display_time);
|
||||
}
|
||||
}
|
||||
|
||||
XrActionSet GHOST_XrActionSet::getActionSet() const
|
||||
{
|
||||
return m_action_set;
|
||||
}
|
||||
|
||||
void *GHOST_XrActionSet::getCustomdata()
|
||||
{
|
||||
if (m_custom_data_ == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return m_custom_data_->custom_data_;
|
||||
}
|
||||
|
||||
void GHOST_XrActionSet::getBindings(
|
||||
std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const
|
||||
{
|
||||
for (auto &[name, action] : m_actions) {
|
||||
action.getBindings(r_bindings);
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup GHOST
|
||||
*/
|
||||
|
||||
/* Note: Requires OpenXR headers to be included before this one for OpenXR types (XrSpace, XrPath,
|
||||
* etc.). */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "GHOST_Util.h"
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
class GHOST_XrActionSpace {
|
||||
public:
|
||||
GHOST_XrActionSpace() = delete; /* Default constructor for map storage. */
|
||||
GHOST_XrActionSpace(XrInstance instance,
|
||||
XrSession session,
|
||||
XrAction action,
|
||||
const GHOST_XrActionSpaceInfo &info,
|
||||
uint32_t subaction_idx);
|
||||
~GHOST_XrActionSpace();
|
||||
|
||||
XrSpace getSpace() const;
|
||||
const XrPath &getSubactionPath() const;
|
||||
|
||||
private:
|
||||
XrSpace m_space = XR_NULL_HANDLE;
|
||||
XrPath m_subaction_path = XR_NULL_PATH;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
class GHOST_XrActionProfile {
|
||||
public:
|
||||
GHOST_XrActionProfile() = delete; /* Default constructor for map storage. */
|
||||
GHOST_XrActionProfile(XrInstance instance,
|
||||
XrAction action,
|
||||
const char *profile_path,
|
||||
const GHOST_XrActionBindingInfo &info);
|
||||
~GHOST_XrActionProfile() = default;
|
||||
|
||||
void getBindings(XrAction action,
|
||||
std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const;
|
||||
|
||||
private:
|
||||
XrPath m_profile = XR_NULL_PATH;
|
||||
/* Bindings identified by interaction (user (subaction) + component) path. */
|
||||
std::map<std::string, XrPath> m_bindings;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
class GHOST_XrAction {
|
||||
public:
|
||||
GHOST_XrAction() = delete; /* Default constructor for map storage. */
|
||||
GHOST_XrAction(XrInstance instance, XrActionSet action_set, const GHOST_XrActionInfo &info);
|
||||
~GHOST_XrAction();
|
||||
|
||||
bool createSpace(XrInstance instance, XrSession session, const GHOST_XrActionSpaceInfo &info);
|
||||
void destroySpace(const char *subaction_path);
|
||||
|
||||
bool createBinding(XrInstance instance,
|
||||
const char *profile_path,
|
||||
const GHOST_XrActionBindingInfo &info);
|
||||
void destroyBinding(const char *profile_path);
|
||||
|
||||
void updateState(XrSession session,
|
||||
const char *action_name,
|
||||
XrSpace reference_space,
|
||||
const XrTime &predicted_display_time);
|
||||
void applyHapticFeedback(XrSession session,
|
||||
const char *action_name,
|
||||
const GHOST_TInt64 &duration,
|
||||
const float &frequency,
|
||||
const float &litude);
|
||||
void stopHapticFeedback(XrSession session, const char *action_name);
|
||||
|
||||
void *getCustomdata();
|
||||
void getBindings(std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const;
|
||||
|
||||
private:
|
||||
XrAction m_action = XR_NULL_HANDLE;
|
||||
GHOST_XrActionType m_type;
|
||||
std::vector<XrPath> m_subaction_paths;
|
||||
/** States for each subaction path. */
|
||||
void *m_states;
|
||||
|
||||
std::unique_ptr<GHOST_C_CustomDataWrapper> m_custom_data_ = nullptr; /* wmXrAction */
|
||||
|
||||
/* Spaces identified by user (subaction) path. */
|
||||
std::map<std::string, GHOST_XrActionSpace> m_spaces;
|
||||
/* Profiles identified by interaction profile path. */
|
||||
std::map<std::string, GHOST_XrActionProfile> m_profiles;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
class GHOST_XrActionSet {
|
||||
public:
|
||||
GHOST_XrActionSet() = delete; /* Default constructor for map storage. */
|
||||
GHOST_XrActionSet(XrInstance instance, const GHOST_XrActionSetInfo &info);
|
||||
~GHOST_XrActionSet();
|
||||
|
||||
bool createAction(XrInstance instance, const GHOST_XrActionInfo &info);
|
||||
void destroyAction(const char *action_name);
|
||||
GHOST_XrAction *findAction(const char *action_name);
|
||||
|
||||
void updateStates(XrSession session,
|
||||
XrSpace reference_space,
|
||||
const XrTime &predicted_display_time);
|
||||
|
||||
XrActionSet getActionSet() const;
|
||||
void *getCustomdata();
|
||||
void getBindings(std::map<XrPath, std::vector<XrActionSuggestedBinding>> &r_bindings) const;
|
||||
|
||||
private:
|
||||
XrActionSet m_action_set = XR_NULL_HANDLE;
|
||||
|
||||
std::unique_ptr<GHOST_C_CustomDataWrapper> m_custom_data_ = nullptr; /* wmXrActionSet */
|
||||
|
||||
std::map<std::string, GHOST_XrAction> m_actions;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
|
@ -246,7 +246,7 @@ void GHOST_XrContext::dispatchErrorMessage(const GHOST_XrException *exception) c
|
|||
{
|
||||
GHOST_XrError error;
|
||||
|
||||
error.user_message = exception->m_msg;
|
||||
error.user_message = exception->m_msg.data();
|
||||
error.customdata = s_error_handler_customdata;
|
||||
|
||||
if (isDebugMode()) {
|
||||
|
@ -374,7 +374,7 @@ void GHOST_XrContext::getAPILayersToEnable(std::vector<const char *> &r_ext_name
|
|||
|
||||
for (const std::string &layer : try_layers) {
|
||||
if (openxr_layer_is_available(m_oxr->layers, layer)) {
|
||||
r_ext_names.push_back(layer.c_str());
|
||||
r_ext_names.push_back(layer.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -484,6 +484,7 @@ GHOST_TXrGraphicsBinding GHOST_XrContext::determineGraphicsBindingTypeToUse(
|
|||
|
||||
void GHOST_XrContext::startSession(const GHOST_XrSessionBeginInfo *begin_info)
|
||||
{
|
||||
m_custom_funcs.session_create_fn = begin_info->create_fn;
|
||||
m_custom_funcs.session_exit_fn = begin_info->exit_fn;
|
||||
m_custom_funcs.session_exit_customdata = begin_info->exit_customdata;
|
||||
|
||||
|
@ -534,6 +535,16 @@ void GHOST_XrContext::handleSessionStateChange(const XrEventDataSessionStateChan
|
|||
* Public as in, exposed in the Ghost API.
|
||||
* \{ */
|
||||
|
||||
GHOST_XrSession *GHOST_XrContext::getSession()
|
||||
{
|
||||
return m_session.get();
|
||||
}
|
||||
|
||||
const GHOST_XrSession *GHOST_XrContext::getSession() const
|
||||
{
|
||||
return m_session.get();
|
||||
}
|
||||
|
||||
void GHOST_XrContext::setGraphicsContextBindFuncs(GHOST_XrGraphicsContextBindFn bind_fn,
|
||||
GHOST_XrGraphicsContextUnbindFn unbind_fn)
|
||||
{
|
||||
|
|
|
@ -35,6 +35,7 @@ struct GHOST_XrCustomFuncs {
|
|||
/** Function to release (possibly free) a graphics context. */
|
||||
GHOST_XrGraphicsContextUnbindFn gpu_ctx_unbind_fn = nullptr;
|
||||
|
||||
GHOST_XrSessionCreateFn session_create_fn = nullptr;
|
||||
GHOST_XrSessionExitFn session_exit_fn = nullptr;
|
||||
void *session_exit_customdata = nullptr;
|
||||
|
||||
|
@ -72,6 +73,10 @@ class GHOST_XrContext : public GHOST_IXrContext {
|
|||
bool isSessionRunning() const override;
|
||||
void drawSessionViews(void *draw_customdata) override;
|
||||
|
||||
/** Needed for the GHOST C api. */
|
||||
GHOST_XrSession *getSession() override;
|
||||
const GHOST_XrSession *getSession() const override;
|
||||
|
||||
static void setErrorHandler(GHOST_XrErrorHandlerFn handler_fn, void *customdata);
|
||||
void dispatchErrorMessage(const class GHOST_XrException *exception) const override;
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
class GHOST_XrException : public std::exception {
|
||||
friend class GHOST_XrContext;
|
||||
|
@ -33,10 +34,10 @@ class GHOST_XrException : public std::exception {
|
|||
|
||||
const char *what() const noexcept override
|
||||
{
|
||||
return m_msg;
|
||||
return m_msg.data();
|
||||
}
|
||||
|
||||
private:
|
||||
const char *m_msg;
|
||||
std::string m_msg;
|
||||
int m_result;
|
||||
};
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "GHOST_C-api.h"
|
||||
|
||||
#include "GHOST_IXrGraphicsBinding.h"
|
||||
#include "GHOST_XrAction.h"
|
||||
#include "GHOST_XrContext.h"
|
||||
#include "GHOST_XrException.h"
|
||||
#include "GHOST_XrSwapchain.h"
|
||||
|
@ -46,6 +47,8 @@ struct OpenXRSessionData {
|
|||
XrSpace view_space;
|
||||
std::vector<XrView> views;
|
||||
std::vector<GHOST_XrSwapchain> swapchains;
|
||||
|
||||
std::map<std::string, GHOST_XrActionSet> action_sets;
|
||||
};
|
||||
|
||||
struct GHOST_XrDrawInfo {
|
||||
|
@ -71,6 +74,7 @@ GHOST_XrSession::~GHOST_XrSession()
|
|||
unbindGraphicsContext();
|
||||
|
||||
m_oxr->swapchains.clear();
|
||||
m_oxr->action_sets.clear();
|
||||
|
||||
if (m_oxr->reference_space != XR_NULL_HANDLE) {
|
||||
CHECK_XR_ASSERT(xrDestroySpace(m_oxr->reference_space));
|
||||
|
@ -177,7 +181,7 @@ void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info)
|
|||
std::ostringstream strstream;
|
||||
strstream << "Available graphics context version does not meet the following requirements: "
|
||||
<< requirement_str;
|
||||
throw GHOST_XrException(strstream.str().c_str());
|
||||
throw GHOST_XrException(strstream.str().data());
|
||||
}
|
||||
m_gpu_binding->initFromGhostContext(*m_gpu_ctx);
|
||||
|
||||
|
@ -194,6 +198,9 @@ void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info)
|
|||
|
||||
prepareDrawing();
|
||||
create_reference_spaces(*m_oxr, begin_info->base_pose);
|
||||
|
||||
/* Create and bind actions here. */
|
||||
m_context->getCustomFuncs().session_create_fn();
|
||||
}
|
||||
|
||||
void GHOST_XrSession::requestEnd()
|
||||
|
@ -223,10 +230,9 @@ GHOST_XrSession::LifeExpectancy GHOST_XrSession::handleStateChangeEvent(
|
|||
assert(m_oxr->session == XR_NULL_HANDLE || m_oxr->session == lifecycle.session);
|
||||
|
||||
switch (lifecycle.state) {
|
||||
case XR_SESSION_STATE_READY: {
|
||||
case XR_SESSION_STATE_READY:
|
||||
beginSession();
|
||||
break;
|
||||
}
|
||||
case XR_SESSION_STATE_STOPPING:
|
||||
endSession();
|
||||
break;
|
||||
|
@ -349,18 +355,6 @@ void GHOST_XrSession::draw(void *draw_customdata)
|
|||
endFrameDrawing(layers);
|
||||
}
|
||||
|
||||
static void copy_openxr_pose_to_ghost_pose(const XrPosef &oxr_pose, GHOST_XrPose &r_ghost_pose)
|
||||
{
|
||||
/* Set and convert to Blender coordinate space. */
|
||||
r_ghost_pose.position[0] = oxr_pose.position.x;
|
||||
r_ghost_pose.position[1] = oxr_pose.position.y;
|
||||
r_ghost_pose.position[2] = oxr_pose.position.z;
|
||||
r_ghost_pose.orientation_quat[0] = oxr_pose.orientation.w;
|
||||
r_ghost_pose.orientation_quat[1] = oxr_pose.orientation.x;
|
||||
r_ghost_pose.orientation_quat[2] = oxr_pose.orientation.y;
|
||||
r_ghost_pose.orientation_quat[3] = oxr_pose.orientation.z;
|
||||
}
|
||||
|
||||
static void ghost_xr_draw_view_info_from_view(const XrView &view, GHOST_XrDrawViewInfo &r_info)
|
||||
{
|
||||
/* Set and convert to Blender coordinate space. */
|
||||
|
@ -501,3 +495,340 @@ void GHOST_XrSession::unbindGraphicsContext()
|
|||
}
|
||||
|
||||
/** \} */ /* Graphics Context Injection */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Actions
|
||||
*
|
||||
* \{ */
|
||||
|
||||
static GHOST_XrActionSet *find_action_set(OpenXRSessionData *oxr, const char *action_set_name)
|
||||
{
|
||||
std::map<std::string, GHOST_XrActionSet>::iterator it = oxr->action_sets.find(action_set_name);
|
||||
if (it == oxr->action_sets.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
bool GHOST_XrSession::createActionSet(const GHOST_XrActionSetInfo &info)
|
||||
{
|
||||
std::map<std::string, GHOST_XrActionSet> &action_sets = m_oxr->action_sets;
|
||||
if (action_sets.find(info.name) != action_sets.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
XrInstance instance = m_context->getInstance();
|
||||
|
||||
action_sets.emplace(
|
||||
std::piecewise_construct, std::make_tuple(info.name), std::make_tuple(instance, info));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GHOST_XrSession::destroyActionSet(const char *action_set_name)
|
||||
{
|
||||
std::map<std::string, GHOST_XrActionSet> &action_sets = m_oxr->action_sets;
|
||||
if (action_sets.find(action_set_name) != action_sets.end()) {
|
||||
action_sets.erase(action_set_name);
|
||||
}
|
||||
}
|
||||
|
||||
bool GHOST_XrSession::createActions(const char *action_set_name,
|
||||
uint32_t count,
|
||||
const GHOST_XrActionInfo *infos)
|
||||
{
|
||||
GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
|
||||
if (action_set == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
XrInstance instance = m_context->getInstance();
|
||||
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
if (!action_set->createAction(instance, infos[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GHOST_XrSession::destroyActions(const char *action_set_name,
|
||||
uint32_t count,
|
||||
const char *const *action_names)
|
||||
{
|
||||
GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
|
||||
if (action_set == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
action_set->destroyAction(action_names[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool GHOST_XrSession::createActionSpaces(const char *action_set_name,
|
||||
uint32_t count,
|
||||
const GHOST_XrActionSpaceInfo *infos)
|
||||
{
|
||||
GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
|
||||
if (action_set == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
XrInstance instance = m_context->getInstance();
|
||||
XrSession session = m_oxr->session;
|
||||
|
||||
for (uint32_t action_idx = 0; action_idx < count; ++action_idx) {
|
||||
const GHOST_XrActionSpaceInfo &info = infos[action_idx];
|
||||
|
||||
GHOST_XrAction *action = action_set->findAction(info.action_name);
|
||||
if (action == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!action->createSpace(instance, session, info)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GHOST_XrSession::destroyActionSpaces(const char *action_set_name,
|
||||
uint32_t count,
|
||||
const GHOST_XrActionSpaceInfo *infos)
|
||||
{
|
||||
GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
|
||||
if (action_set == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t action_idx = 0; action_idx < count; ++action_idx) {
|
||||
const GHOST_XrActionSpaceInfo &info = infos[action_idx];
|
||||
|
||||
GHOST_XrAction *action = action_set->findAction(info.action_name);
|
||||
if (action == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (uint32_t subaction_idx = 0; subaction_idx < info.count_subaction_paths; ++subaction_idx) {
|
||||
action->destroySpace(info.subaction_paths[subaction_idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GHOST_XrSession::createActionBindings(const char *action_set_name,
|
||||
uint32_t count,
|
||||
const GHOST_XrActionProfileInfo *infos)
|
||||
{
|
||||
GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
|
||||
if (action_set == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
XrInstance instance = m_context->getInstance();
|
||||
|
||||
for (uint32_t profile_idx = 0; profile_idx < count; ++profile_idx) {
|
||||
const GHOST_XrActionProfileInfo &info = infos[profile_idx];
|
||||
const char *profile_path = info.profile_path;
|
||||
|
||||
for (uint32_t binding_idx = 0; binding_idx < info.count_bindings; ++binding_idx) {
|
||||
const GHOST_XrActionBindingInfo &binding = info.bindings[binding_idx];
|
||||
|
||||
GHOST_XrAction *action = action_set->findAction(binding.action_name);
|
||||
if (action == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
action->createBinding(instance, profile_path, binding);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GHOST_XrSession::destroyActionBindings(const char *action_set_name,
|
||||
uint32_t count,
|
||||
const GHOST_XrActionProfileInfo *infos)
|
||||
{
|
||||
GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
|
||||
if (action_set == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t profile_idx = 0; profile_idx < count; ++profile_idx) {
|
||||
const GHOST_XrActionProfileInfo &info = infos[profile_idx];
|
||||
const char *profile_path = info.profile_path;
|
||||
|
||||
for (uint32_t binding_idx = 0; binding_idx < info.count_bindings; ++binding_idx) {
|
||||
const GHOST_XrActionBindingInfo &binding = info.bindings[binding_idx];
|
||||
|
||||
GHOST_XrAction *action = action_set->findAction(binding.action_name);
|
||||
if (action == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
action->destroyBinding(profile_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GHOST_XrSession::attachActionSets()
|
||||
{
|
||||
/* Suggest action bindings for all action sets. */
|
||||
std::map<XrPath, std::vector<XrActionSuggestedBinding>> profile_bindings;
|
||||
for (auto &[name, action_set] : m_oxr->action_sets) {
|
||||
action_set.getBindings(profile_bindings);
|
||||
}
|
||||
|
||||
if (profile_bindings.size() < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
XrInteractionProfileSuggestedBinding bindings_info{
|
||||
XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING};
|
||||
XrInstance instance = m_context->getInstance();
|
||||
|
||||
for (auto &[profile, bindings] : profile_bindings) {
|
||||
bindings_info.interactionProfile = profile;
|
||||
bindings_info.countSuggestedBindings = (uint32_t)bindings.size();
|
||||
bindings_info.suggestedBindings = bindings.data();
|
||||
|
||||
CHECK_XR(xrSuggestInteractionProfileBindings(instance, &bindings_info),
|
||||
"Failed to suggest interaction profile bindings.");
|
||||
}
|
||||
|
||||
/* Attach action sets. */
|
||||
XrSessionActionSetsAttachInfo attach_info{XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO};
|
||||
attach_info.countActionSets = (uint32_t)m_oxr->action_sets.size();
|
||||
|
||||
/* Create an aligned copy of the action sets to pass to xrAttachSessionActionSets(). */
|
||||
std::vector<XrActionSet> action_sets(attach_info.countActionSets);
|
||||
uint32_t i = 0;
|
||||
for (auto &[name, action_set] : m_oxr->action_sets) {
|
||||
action_sets[i++] = action_set.getActionSet();
|
||||
}
|
||||
attach_info.actionSets = action_sets.data();
|
||||
|
||||
CHECK_XR(xrAttachSessionActionSets(m_oxr->session, &attach_info),
|
||||
"Failed to attach XR action sets.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GHOST_XrSession::syncActions(const char *action_set_name)
|
||||
{
|
||||
std::map<std::string, GHOST_XrActionSet> &action_sets = m_oxr->action_sets;
|
||||
|
||||
XrActionsSyncInfo sync_info{XR_TYPE_ACTIONS_SYNC_INFO};
|
||||
sync_info.countActiveActionSets = (action_set_name != nullptr) ? 1 :
|
||||
(uint32_t)action_sets.size();
|
||||
if (sync_info.countActiveActionSets < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<XrActiveActionSet> active_action_sets(sync_info.countActiveActionSets);
|
||||
GHOST_XrActionSet *action_set = nullptr;
|
||||
|
||||
if (action_set_name != nullptr) {
|
||||
action_set = find_action_set(m_oxr.get(), action_set_name);
|
||||
if (action_set == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
XrActiveActionSet &active_action_set = active_action_sets[0];
|
||||
active_action_set.actionSet = action_set->getActionSet();
|
||||
active_action_set.subactionPath = XR_NULL_PATH;
|
||||
}
|
||||
else {
|
||||
uint32_t i = 0;
|
||||
for (auto &[name, action_set] : action_sets) {
|
||||
XrActiveActionSet &active_action_set = active_action_sets[i++];
|
||||
active_action_set.actionSet = action_set.getActionSet();
|
||||
active_action_set.subactionPath = XR_NULL_PATH;
|
||||
}
|
||||
}
|
||||
sync_info.activeActionSets = active_action_sets.data();
|
||||
|
||||
CHECK_XR(xrSyncActions(m_oxr->session, &sync_info), "Failed to synchronize XR actions.");
|
||||
|
||||
/* Update action states (i.e. Blender custom data). */
|
||||
XrSession session = m_oxr->session;
|
||||
XrSpace reference_space = m_oxr->reference_space;
|
||||
const XrTime &predicted_display_time = m_draw_info->frame_state.predictedDisplayTime;
|
||||
|
||||
if (action_set != nullptr) {
|
||||
action_set->updateStates(session, reference_space, predicted_display_time);
|
||||
}
|
||||
else {
|
||||
for (auto &[name, action_set] : action_sets) {
|
||||
action_set.updateStates(session, reference_space, predicted_display_time);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GHOST_XrSession::applyHapticAction(const char *action_set_name,
|
||||
const char *action_name,
|
||||
const GHOST_TInt64 &duration,
|
||||
const float &frequency,
|
||||
const float &litude)
|
||||
{
|
||||
GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
|
||||
if (action_set == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GHOST_XrAction *action = action_set->findAction(action_name);
|
||||
if (action == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
action->applyHapticFeedback(m_oxr->session, action_name, duration, frequency, amplitude);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GHOST_XrSession::stopHapticAction(const char *action_set_name, const char *action_name)
|
||||
{
|
||||
GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
|
||||
if (action_set == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
GHOST_XrAction *action = action_set->findAction(action_name);
|
||||
if (action == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
action->stopHapticFeedback(m_oxr->session, action_name);
|
||||
}
|
||||
|
||||
void *GHOST_XrSession::getActionSetCustomdata(const char *action_set_name)
|
||||
{
|
||||
GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
|
||||
if (action_set == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return action_set->getCustomdata();
|
||||
}
|
||||
|
||||
void *GHOST_XrSession::getActionCustomdata(const char *action_set_name, const char *action_name)
|
||||
{
|
||||
GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
|
||||
if (action_set == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GHOST_XrAction *action = action_set->findAction(action_name);
|
||||
if (action == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return action->getCustomdata();
|
||||
}
|
||||
|
||||
/** \} */ /* Actions */
|
||||
|
|
|
@ -52,6 +52,43 @@ class GHOST_XrSession {
|
|||
|
||||
void draw(void *draw_customdata);
|
||||
|
||||
/** Action functions to be called pre-session start.
|
||||
* Note: The "destroy" functions can also be called post-session start. */
|
||||
bool createActionSet(const GHOST_XrActionSetInfo &info);
|
||||
void destroyActionSet(const char *action_set_name);
|
||||
bool createActions(const char *action_set_name, uint32_t count, const GHOST_XrActionInfo *infos);
|
||||
void destroyActions(const char *action_set_name,
|
||||
uint32_t count,
|
||||
const char *const *action_names);
|
||||
bool createActionSpaces(const char *action_set_name,
|
||||
uint32_t count,
|
||||
const GHOST_XrActionSpaceInfo *infos);
|
||||
void destroyActionSpaces(const char *action_set_name,
|
||||
uint32_t count,
|
||||
const GHOST_XrActionSpaceInfo *infos);
|
||||
bool createActionBindings(const char *action_set_name,
|
||||
uint32_t count,
|
||||
const GHOST_XrActionProfileInfo *infos);
|
||||
void destroyActionBindings(const char *action_set_name,
|
||||
uint32_t count,
|
||||
const GHOST_XrActionProfileInfo *infos);
|
||||
bool attachActionSets();
|
||||
|
||||
/** Action functions to be called post-session start. */
|
||||
bool syncActions(
|
||||
const char *action_set_name = nullptr); /* If action_set_name is nullptr, all attached
|
||||
* action sets will be synced. */
|
||||
bool applyHapticAction(const char *action_set_name,
|
||||
const char *action_name,
|
||||
const GHOST_TInt64 &duration,
|
||||
const float &frequency,
|
||||
const float &litude);
|
||||
void stopHapticAction(const char *action_set_name, const char *action_name);
|
||||
|
||||
/* Custom data (owned by Blender, not GHOST) accessors. */
|
||||
void *getActionSetCustomdata(const char *action_set_name);
|
||||
void *getActionCustomdata(const char *action_set_name, const char *action_name);
|
||||
|
||||
private:
|
||||
/** Pointer back to context managing this session. Would be nice to avoid, but needed to access
|
||||
* custom callbacks set before session start. */
|
||||
|
|
|
@ -45,3 +45,27 @@
|
|||
(void)_res; \
|
||||
} \
|
||||
(void)0
|
||||
|
||||
inline void copy_ghost_pose_to_openxr_pose(const GHOST_XrPose &ghost_pose, XrPosef &r_oxr_pose)
|
||||
{
|
||||
/* Set and convert to OpenXR coodinate space. */
|
||||
r_oxr_pose.position.x = ghost_pose.position[0];
|
||||
r_oxr_pose.position.y = ghost_pose.position[1];
|
||||
r_oxr_pose.position.z = ghost_pose.position[2];
|
||||
r_oxr_pose.orientation.w = ghost_pose.orientation_quat[0];
|
||||
r_oxr_pose.orientation.x = ghost_pose.orientation_quat[1];
|
||||
r_oxr_pose.orientation.y = ghost_pose.orientation_quat[2];
|
||||
r_oxr_pose.orientation.z = ghost_pose.orientation_quat[3];
|
||||
}
|
||||
|
||||
inline void copy_openxr_pose_to_ghost_pose(const XrPosef &oxr_pose, GHOST_XrPose &r_ghost_pose)
|
||||
{
|
||||
/* Set and convert to Blender coordinate space. */
|
||||
r_ghost_pose.position[0] = oxr_pose.position.x;
|
||||
r_ghost_pose.position[1] = oxr_pose.position.y;
|
||||
r_ghost_pose.position[2] = oxr_pose.position.z;
|
||||
r_ghost_pose.orientation_quat[0] = oxr_pose.orientation.w;
|
||||
r_ghost_pose.orientation_quat[1] = oxr_pose.orientation.x;
|
||||
r_ghost_pose.orientation_quat[2] = oxr_pose.orientation.y;
|
||||
r_ghost_pose.orientation_quat[3] = oxr_pose.orientation.z;
|
||||
}
|
||||
|
|
|
@ -29,10 +29,10 @@ class Version:
|
|||
|
||||
|
||||
def get_download_file_names(version: Version):
|
||||
yield f"blender-{version}-linux64.tar.xz"
|
||||
yield f"blender-{version}-macOS.dmg"
|
||||
yield f"blender-{version}-windows64.msi"
|
||||
yield f"blender-{version}-windows64.zip"
|
||||
yield f"blender-{version}-linux-x86_64.tar.xz"
|
||||
yield f"blender-{version}-darwin-x86_64.dmg"
|
||||
yield f"blender-{version}-windows-amd64.msi"
|
||||
yield f"blender-{version}-windows-amd64.zip"
|
||||
|
||||
|
||||
def get_download_url(version: Version, file_name: str) -> str:
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit bb16aba5bd3873794eefe167497118b6063b9a85
|
||||
Subproject commit 4fcdbfe7c20edfc1204c0aa46c98ea25354abcd9
|
|
@ -543,22 +543,6 @@ def module_bl_info(mod, info_basis=None):
|
|||
if not addon_info["name"]:
|
||||
addon_info["name"] = mod.__name__
|
||||
|
||||
# Replace 'wiki_url' with 'doc_url'.
|
||||
doc_url = addon_info.pop("wiki_url", None)
|
||||
if doc_url is not None:
|
||||
# Unlikely, but possible that both are set.
|
||||
if not addon_info["doc_url"]:
|
||||
addon_info["doc_url"] = doc_url
|
||||
if _bpy.app.debug:
|
||||
print(
|
||||
"Warning: add-on \"%s\": 'wiki_url' in 'bl_info' "
|
||||
"is deprecated please use 'doc_url' instead!\n"
|
||||
" %s" % (
|
||||
addon_info['name'],
|
||||
getattr(mod, "__file__", None),
|
||||
)
|
||||
)
|
||||
|
||||
doc_url = addon_info["doc_url"]
|
||||
if doc_url:
|
||||
doc_url_prefix = "{BLENDER_MANUAL_URL}"
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
def url_prefill_from_blender(addon_info=None):
|
||||
import bpy
|
||||
import bgl
|
||||
import gpu
|
||||
import struct
|
||||
import platform
|
||||
import urllib.parse
|
||||
|
@ -38,9 +38,9 @@ def url_prefill_from_blender(addon_info=None):
|
|||
)
|
||||
fh.write(
|
||||
"Graphics card: %s %s %s\n" % (
|
||||
bgl.glGetString(bgl.GL_RENDERER),
|
||||
bgl.glGetString(bgl.GL_VENDOR),
|
||||
bgl.glGetString(bgl.GL_VERSION),
|
||||
gpu.platform.renderer_get(),
|
||||
gpu.platform.vendor_get(),
|
||||
gpu.platform.version_get(),
|
||||
)
|
||||
)
|
||||
fh.write(
|
||||
|
|
|
@ -370,7 +370,7 @@ def module_names(path, recursive=False):
|
|||
|
||||
def basename(path):
|
||||
"""
|
||||
Equivalent to os.path.basename, but skips a "//" prefix.
|
||||
Equivalent to ``os.path.basename``, but skips a "//" prefix.
|
||||
|
||||
Use for Windows compatibility.
|
||||
"""
|
||||
|
|
|
@ -235,7 +235,7 @@ def draw(layout, context, context_member, property_type, use_edit=True):
|
|||
|
||||
assert(isinstance(rna_item, property_type))
|
||||
|
||||
items = rna_item.items()
|
||||
items = list(rna_item.items())
|
||||
items.sort()
|
||||
|
||||
# TODO: Allow/support adding new custom props to overrides.
|
||||
|
|
|
@ -28,7 +28,7 @@ def write_sysinfo(filepath):
|
|||
import subprocess
|
||||
|
||||
import bpy
|
||||
import bgl
|
||||
import gpu
|
||||
|
||||
# pretty repr
|
||||
def prepr(v):
|
||||
|
@ -190,46 +190,29 @@ def write_sysinfo(filepath):
|
|||
if bpy.app.background:
|
||||
output.write("\nOpenGL: missing, background mode\n")
|
||||
else:
|
||||
output.write(title("OpenGL"))
|
||||
version = bgl.glGetString(bgl.GL_RENDERER)
|
||||
output.write("renderer:\t%r\n" % version)
|
||||
output.write("vendor:\t\t%r\n" % (bgl.glGetString(bgl.GL_VENDOR)))
|
||||
output.write("version:\t%r\n" % (bgl.glGetString(bgl.GL_VERSION)))
|
||||
output.write(title("GPU"))
|
||||
output.write("renderer:\t%r\n" % gpu.platform.renderer_get())
|
||||
output.write("vendor:\t\t%r\n" % gpu.platform.vendor_get())
|
||||
output.write("version:\t%r\n" % gpu.platform.version_get())
|
||||
output.write("extensions:\n")
|
||||
|
||||
limit = bgl.Buffer(bgl.GL_INT, 1)
|
||||
bgl.glGetIntegerv(bgl.GL_NUM_EXTENSIONS, limit)
|
||||
|
||||
glext = []
|
||||
for i in range(limit[0]):
|
||||
glext.append(bgl.glGetStringi(bgl.GL_EXTENSIONS, i))
|
||||
|
||||
glext = sorted(glext)
|
||||
glext = sorted(gpu.capabilities.extensions_get())
|
||||
|
||||
for l in glext:
|
||||
output.write("\t%s\n" % l)
|
||||
|
||||
output.write(title("Implementation Dependent OpenGL Limits"))
|
||||
bgl.glGetIntegerv(bgl.GL_MAX_ELEMENTS_VERTICES, limit)
|
||||
output.write("Maximum DrawElements Vertices:\t%d\n" % limit[0])
|
||||
bgl.glGetIntegerv(bgl.GL_MAX_ELEMENTS_INDICES, limit)
|
||||
output.write("Maximum DrawElements Indices:\t%d\n" % limit[0])
|
||||
output.write(title("Implementation Dependent GPU Limits"))
|
||||
output.write("Maximum Batch Vertices:\t%d\n" % gpu.capabilities.max_batch_vertices_get())
|
||||
output.write("Maximum Batch Indices:\t%d\n" % gpu.capabilities.max_batch_indices_get())
|
||||
|
||||
output.write("\nGLSL:\n")
|
||||
bgl.glGetIntegerv(bgl.GL_MAX_VARYING_FLOATS, limit)
|
||||
output.write("Maximum Varying Floats:\t%d\n" % limit[0])
|
||||
bgl.glGetIntegerv(bgl.GL_MAX_VERTEX_ATTRIBS, limit)
|
||||
output.write("Maximum Vertex Attributes:\t%d\n" % limit[0])
|
||||
bgl.glGetIntegerv(bgl.GL_MAX_VERTEX_UNIFORM_COMPONENTS, limit)
|
||||
output.write("Maximum Vertex Uniform Components:\t%d\n" % limit[0])
|
||||
bgl.glGetIntegerv(bgl.GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, limit)
|
||||
output.write("Maximum Fragment Uniform Components:\t%d\n" % limit[0])
|
||||
bgl.glGetIntegerv(bgl.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, limit)
|
||||
output.write("Maximum Vertex Image Units:\t%d\n" % limit[0])
|
||||
bgl.glGetIntegerv(bgl.GL_MAX_TEXTURE_IMAGE_UNITS, limit)
|
||||
output.write("Maximum Fragment Image Units:\t%d\n" % limit[0])
|
||||
bgl.glGetIntegerv(bgl.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, limit)
|
||||
output.write("Maximum Pipeline Image Units:\t%d\n" % limit[0])
|
||||
output.write("Maximum Varying Floats:\t%d\n" % gpu.capabilities.max_varying_floats_get())
|
||||
output.write("Maximum Vertex Attributes:\t%d\n" % gpu.capabilities.max_vertex_attribs_get())
|
||||
output.write("Maximum Vertex Uniform Components:\t%d\n" % gpu.capabilities.max_uniforms_vert_get())
|
||||
output.write("Maximum Fragment Uniform Components:\t%d\n" % gpu.capabilities.max_uniforms_frag_get())
|
||||
output.write("Maximum Vertex Image Units:\t%d\n" % gpu.capabilities.max_textures_vert_get())
|
||||
output.write("Maximum Fragment Image Units:\t%d\n" % gpu.capabilities.max_textures_frag_get())
|
||||
output.write("Maximum Pipeline Image Units:\t%d\n" % gpu.capabilities.max_textures_get())
|
||||
|
||||
if bpy.app.build_options.cycles:
|
||||
import cycles
|
||||
|
|
|
@ -3333,8 +3333,6 @@ def km_weight_paint(params):
|
|||
*_template_paint_radial_control("weight_paint"),
|
||||
("wm.context_toggle", {"type": 'M', "value": 'PRESS'},
|
||||
{"properties": [("data_path", 'weight_paint_object.data.use_paint_mask')]}),
|
||||
("wm.context_toggle", {"type": 'V', "value": 'PRESS'},
|
||||
{"properties": [("data_path", 'weight_paint_object.data.use_paint_mask_vertex')]}),
|
||||
("wm.context_toggle", {"type": 'S', "value": 'PRESS', "shift": True},
|
||||
{"properties": [("data_path", 'tool_settings.weight_paint.brush.use_smooth_stroke')]}),
|
||||
*_template_items_context_panel("VIEW3D_PT_paint_weight_context_menu", {"type": 'RIGHTMOUSE', "value": 'PRESS'}),
|
||||
|
|
|
@ -590,7 +590,7 @@ class PREFERENCES_OT_addon_install(Operator):
|
|||
name="Target Path",
|
||||
items=(
|
||||
('DEFAULT', "Default", ""),
|
||||
('PREFS', "User Prefs", ""),
|
||||
('PREFS', "Preferences", ""),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
@ -113,8 +113,8 @@ class GPENCIL_MT_layer_context_menu(Menu):
|
|||
layout.operator("gpencil.layer_merge", icon='SORT_ASC', text="Merge Down")
|
||||
|
||||
layout.separator()
|
||||
layout.menu("VIEW3D_MT_gpencil_append_active_layer")
|
||||
layout.menu("VIEW3D_MT_gpencil_append_all_layers")
|
||||
layout.operator("gpencil.layer_duplicate_object", text="Copy Layer to Selected").only_active=True
|
||||
layout.operator("gpencil.layer_duplicate_object", text="Copy All Layers to Selected").only_active=False
|
||||
|
||||
|
||||
class DATA_PT_gpencil_layers(DataButtonsPanel, Panel):
|
||||
|
|
|
@ -46,16 +46,18 @@ class GPENCIL_MT_material_context_menu(Menu):
|
|||
|
||||
layout.separator()
|
||||
|
||||
layout.operator("object.material_slot_remove_unused")
|
||||
layout.operator("gpencil.stroke_merge_material", text="Merge Similar")
|
||||
|
||||
layout.separator()
|
||||
layout.operator("gpencil.material_to_vertex_color", text="Convert Materials to Vertex Color")
|
||||
layout.operator("gpencil.extract_palette_vertex", text="Extract Palette from Vertex Color")
|
||||
|
||||
layout.separator()
|
||||
layout.menu("VIEW3D_MT_gpencil_append_active_material")
|
||||
layout.menu("VIEW3D_MT_gpencil_append_all_materials")
|
||||
|
||||
layout.operator("gpencil.materials_copy_to_object", text="Copy Material to Selected").only_active = True
|
||||
layout.operator("gpencil.materials_copy_to_object", text="Copy All Materials to Selected").only_active = False
|
||||
|
||||
layout.separator()
|
||||
|
||||
layout.operator("gpencil.stroke_merge_material", text="Merge Similar")
|
||||
layout.operator("object.material_slot_remove_unused")
|
||||
|
||||
|
||||
class GPENCIL_UL_matslots(UIList):
|
||||
|
|
|
@ -162,13 +162,13 @@ class OUTLINER_MT_collection_view_layer(Menu):
|
|||
layout.operator("outliner.collection_exclude_set")
|
||||
layout.operator("outliner.collection_exclude_clear")
|
||||
|
||||
layout.operator("outliner.collection_holdout_set")
|
||||
layout.operator("outliner.collection_holdout_clear")
|
||||
|
||||
if context.engine == 'CYCLES':
|
||||
layout.operator("outliner.collection_indirect_only_set")
|
||||
layout.operator("outliner.collection_indirect_only_clear")
|
||||
|
||||
layout.operator("outliner.collection_holdout_set")
|
||||
layout.operator("outliner.collection_holdout_clear")
|
||||
|
||||
|
||||
class OUTLINER_MT_collection_visibility(Menu):
|
||||
bl_label = "Visibility"
|
||||
|
|
|
@ -4956,75 +4956,6 @@ class VIEW3D_MT_assign_material(Menu):
|
|||
icon='LAYER_ACTIVE' if mat == mat_active else 'BLANK1').material = mat.name
|
||||
|
||||
|
||||
def gpencil_layer_append_menu_items(context, layout, only_active):
|
||||
done = False
|
||||
view_layer = context.view_layer
|
||||
obact = context.active_object
|
||||
gpl = context.active_gpencil_layer
|
||||
|
||||
done = False
|
||||
if gpl is not None:
|
||||
for ob in view_layer.objects:
|
||||
if ob.type == 'GPENCIL' and ob != obact:
|
||||
op = layout.operator("gpencil.layer_duplicate_object", text=ob.name)
|
||||
op.object = ob.name
|
||||
op.only_active = only_active
|
||||
done = True
|
||||
|
||||
if done is False:
|
||||
layout.label(text="No destination object", icon='ERROR')
|
||||
else:
|
||||
layout.label(text="No layer to copy", icon='ERROR')
|
||||
|
||||
|
||||
class VIEW3D_MT_gpencil_append_active_layer(Menu):
|
||||
bl_label = "Append Active Layer to Object"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
gpencil_layer_append_menu_items(context, layout, True)
|
||||
|
||||
|
||||
class VIEW3D_MT_gpencil_append_all_layers(Menu):
|
||||
bl_label = "Append All Layers to Object"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
gpencil_layer_append_menu_items(context, layout, False)
|
||||
|
||||
|
||||
def gpencil_material_menu_items(context, layout, only_selected):
|
||||
done = False
|
||||
view_layer = context.view_layer
|
||||
obact = context.active_object
|
||||
|
||||
for ob in view_layer.objects:
|
||||
if ob.type == 'GPENCIL' and ob != obact:
|
||||
op = layout.operator("gpencil.materials_append_to_object", text=ob.name)
|
||||
op.object = ob.name
|
||||
op.only_selected = only_selected
|
||||
done = True
|
||||
|
||||
if done is False:
|
||||
layout.label(text="No destination object", icon='ERROR')
|
||||
|
||||
|
||||
class VIEW3D_MT_gpencil_append_active_material(Menu):
|
||||
bl_label = "Append Active Material to Object"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
gpencil_material_menu_items(context, layout, True)
|
||||
|
||||
|
||||
class VIEW3D_MT_gpencil_append_all_materials(Menu):
|
||||
bl_label = "Append All Materials to Object"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
gpencil_material_menu_items(context, layout, False)
|
||||
|
||||
|
||||
class VIEW3D_MT_edit_gpencil(Menu):
|
||||
bl_label = "Grease Pencil"
|
||||
|
||||
|
@ -7689,10 +7620,6 @@ classes = (
|
|||
VIEW3D_MT_weight_gpencil,
|
||||
VIEW3D_MT_gpencil_animation,
|
||||
VIEW3D_MT_gpencil_simplify,
|
||||
VIEW3D_MT_gpencil_append_active_layer,
|
||||
VIEW3D_MT_gpencil_append_all_layers,
|
||||
VIEW3D_MT_gpencil_append_active_material,
|
||||
VIEW3D_MT_gpencil_append_all_materials,
|
||||
VIEW3D_MT_gpencil_autoweights,
|
||||
VIEW3D_MT_gpencil_edit_context_menu,
|
||||
VIEW3D_MT_edit_curve,
|
||||
|
|
|
@ -488,6 +488,7 @@ geometry_node_categories = [
|
|||
NodeItem("GeometryNodeAttributeClamp"),
|
||||
NodeItem("GeometryNodeAttributeCompare"),
|
||||
NodeItem("GeometryNodeAttributeConvert"),
|
||||
NodeItem("GeometryNodeAttributeCache"),
|
||||
NodeItem("GeometryNodeAttributeCurveMap"),
|
||||
NodeItem("GeometryNodeAttributeFill"),
|
||||
NodeItem("GeometryNodeAttributeMix"),
|
||||
|
|
|
@ -27,6 +27,80 @@
|
|||
#include "BLI_color.hh"
|
||||
#include "BLI_float2.hh"
|
||||
#include "BLI_float3.hh"
|
||||
#include "BLI_function_ref.hh"
|
||||
|
||||
/**
|
||||
* Contains information about an attribute in a geometry component.
|
||||
* More information can be added in the future. E.g. whether the attribute is builtin and how it is
|
||||
* stored (uv map, vertex group, ...).
|
||||
*/
|
||||
struct AttributeMetaData {
|
||||
AttributeDomain domain;
|
||||
CustomDataType data_type;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for the attribute intializer types described below.
|
||||
*/
|
||||
struct AttributeInit {
|
||||
enum class Type {
|
||||
Default,
|
||||
VArray,
|
||||
MoveArray,
|
||||
};
|
||||
Type type;
|
||||
AttributeInit(const Type type) : type(type)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an attribute using the default value for the data type.
|
||||
* The default values may depend on the attribute provider implementation.
|
||||
*/
|
||||
struct AttributeInitDefault : public AttributeInit {
|
||||
AttributeInitDefault() : AttributeInit(Type::Default)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an attribute by copying data from an existing virtual array. The virtual array
|
||||
* must have the same type as the newly created attribute.
|
||||
*
|
||||
* Note that this can be used to fill the new attribute with the default
|
||||
*/
|
||||
struct AttributeInitVArray : public AttributeInit {
|
||||
const blender::fn::GVArray *varray;
|
||||
|
||||
AttributeInitVArray(const blender::fn::GVArray *varray)
|
||||
: AttributeInit(Type::VArray), varray(varray)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an attribute with a by passing ownership of a pre-allocated contiguous array of data.
|
||||
* Sometimes data is created before a geometry component is available. In that case, it's
|
||||
* preferable to move data directly to the created attribute to avoid a new allocation and a copy.
|
||||
*
|
||||
* Note that this will only have a benefit for attributes that are stored directly as contiguous
|
||||
* arrays, so not for some built-in attributes.
|
||||
*
|
||||
* The array must be allocated with MEM_*, since `attribute_try_create` will free the array if it
|
||||
* can't be used directly, and that is generally how Blender expects custom data to be allocated.
|
||||
*/
|
||||
struct AttributeInitMove : public AttributeInit {
|
||||
void *data = nullptr;
|
||||
|
||||
AttributeInitMove(void *data) : AttributeInit(Type::MoveArray), data(data)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/* Returns false when the iteration should be stopped. */
|
||||
using AttributeForeachCallback = blender::FunctionRef<bool(blender::StringRefNull attribute_name,
|
||||
const AttributeMetaData &meta_data)>;
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
|
|
|
@ -227,8 +227,10 @@ template<typename T> class SimpleMixer {
|
|||
}
|
||||
};
|
||||
|
||||
/** This mixer accumulates values in a type that is different from the one that is mixed. Some
|
||||
* types cannot encode the floating point weights in their values (e.g. int and bool). */
|
||||
/**
|
||||
* This mixer accumulates values in a type that is different from the one that is mixed.
|
||||
* Some types cannot encode the floating point weights in their values (e.g. int and bool).
|
||||
*/
|
||||
template<typename T, typename AccumulationT, T (*ConvertToT)(const AccumulationT &value)>
|
||||
class SimpleMixerWithAccumulationType {
|
||||
private:
|
||||
|
|
|
@ -59,6 +59,7 @@ typedef enum {
|
|||
BKE_CB_EVT_VERSION_UPDATE,
|
||||
BKE_CB_EVT_LOAD_FACTORY_USERDEF_POST,
|
||||
BKE_CB_EVT_LOAD_FACTORY_STARTUP_POST,
|
||||
BKE_CB_EVT_XR_SESSION_START_PRE,
|
||||
BKE_CB_EVT_TOT,
|
||||
} eCbEvent;
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
#include "BLI_float3.hh"
|
||||
#include "BLI_float4x4.hh"
|
||||
#include "BLI_function_ref.hh"
|
||||
#include "BLI_hash.hh"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_set.hh"
|
||||
|
@ -57,79 +56,6 @@ class ComponentAttributeProviders;
|
|||
|
||||
class GeometryComponent;
|
||||
|
||||
/**
|
||||
* Contains information about an attribute in a geometry component.
|
||||
* More information can be added in the future. E.g. whether the attribute is builtin and how it is
|
||||
* stored (uv map, vertex group, ...).
|
||||
*/
|
||||
struct AttributeMetaData {
|
||||
AttributeDomain domain;
|
||||
CustomDataType data_type;
|
||||
};
|
||||
|
||||
/* Returns false when the iteration should be stopped. */
|
||||
using AttributeForeachCallback = blender::FunctionRef<bool(blender::StringRefNull attribute_name,
|
||||
const AttributeMetaData &meta_data)>;
|
||||
|
||||
/**
|
||||
* Base class for the attribute intializer types described below.
|
||||
*/
|
||||
struct AttributeInit {
|
||||
enum class Type {
|
||||
Default,
|
||||
VArray,
|
||||
MoveArray,
|
||||
};
|
||||
Type type;
|
||||
AttributeInit(const Type type) : type(type)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an attribute using the default value for the data type.
|
||||
* The default values may depend on the attribute provider implementation.
|
||||
*/
|
||||
struct AttributeInitDefault : public AttributeInit {
|
||||
AttributeInitDefault() : AttributeInit(Type::Default)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an attribute by copying data from an existing virtual array. The virtual array
|
||||
* must have the same type as the newly created attribute.
|
||||
*
|
||||
* Note that this can be used to fill the new attribute with the default
|
||||
*/
|
||||
struct AttributeInitVArray : public AttributeInit {
|
||||
const blender::fn::GVArray *varray;
|
||||
|
||||
AttributeInitVArray(const blender::fn::GVArray *varray)
|
||||
: AttributeInit(Type::VArray), varray(varray)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an attribute with a by passing ownership of a pre-allocated contiguous array of data.
|
||||
* Sometimes data is created before a geometry component is available. In that case, it's
|
||||
* preferable to move data directly to the created attribute to avoid a new allocation and a copy.
|
||||
*
|
||||
* Note that this will only have a benefit for attributes that are stored directly as contiguous
|
||||
* arrays, so not for some built-in attributes.
|
||||
*
|
||||
* The array must be allocated with MEM_*, since `attribute_try_create` will free the array if it
|
||||
* can't be used directly, and that is generally how Blender expects custom data to be allocated.
|
||||
*/
|
||||
struct AttributeInitMove : public AttributeInit {
|
||||
void *data = nullptr;
|
||||
|
||||
AttributeInitMove(void *data) : AttributeInit(Type::MoveArray), data(data)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the base class for specialized geometry component types.
|
||||
*/
|
||||
|
|
|
@ -108,7 +108,10 @@ void BKE_gpencil_stroke_select_index_reset(struct bGPDstroke *gps);
|
|||
|
||||
struct bGPDframe *BKE_gpencil_frame_addnew(struct bGPDlayer *gpl, int cframe);
|
||||
struct bGPDframe *BKE_gpencil_frame_addcopy(struct bGPDlayer *gpl, int cframe);
|
||||
struct bGPDlayer *BKE_gpencil_layer_addnew(struct bGPdata *gpd, const char *name, bool setactive);
|
||||
struct bGPDlayer *BKE_gpencil_layer_addnew(struct bGPdata *gpd,
|
||||
const char *name,
|
||||
const bool setactive,
|
||||
const bool add_to_header);
|
||||
struct bGPdata *BKE_gpencil_data_addnew(struct Main *bmain, const char name[]);
|
||||
|
||||
struct bGPDframe *BKE_gpencil_frame_duplicate(const struct bGPDframe *gpf_src,
|
||||
|
|
|
@ -325,6 +325,7 @@ int BKE_image_get_tile_from_pos(struct Image *ima,
|
|||
const float uv[2],
|
||||
float r_uv[2],
|
||||
float r_ofs[2]);
|
||||
int BKE_image_find_nearest_tile(const struct Image *image, const float co[2]);
|
||||
|
||||
void BKE_image_get_size(struct Image *image, struct ImageUser *iuser, int *r_width, int *r_height);
|
||||
void BKE_image_get_size_fl(struct Image *image, struct ImageUser *iuser, float r_size[2]);
|
||||
|
|
|
@ -95,8 +95,16 @@ struct AvailableAttributeInfo {
|
|||
};
|
||||
|
||||
struct NodeUIStorage {
|
||||
std::mutex mutex;
|
||||
blender::Vector<NodeWarning> warnings;
|
||||
blender::Set<AvailableAttributeInfo> attribute_hints;
|
||||
|
||||
NodeUIStorage() = default;
|
||||
/* Needed because the mutex can't be moved or copied. */
|
||||
NodeUIStorage(NodeUIStorage &&other)
|
||||
: warnings(std::move(other.warnings)), attribute_hints(std::move(other.attribute_hints))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct NodeTreeUIStorage {
|
||||
|
|
|
@ -502,7 +502,11 @@ typedef struct SculptSession {
|
|||
/* Total number of polys of the base mesh. */
|
||||
int totfaces;
|
||||
/* Face sets store its visibility in the sign of the integer, using the absolute value as the
|
||||
* Face Set ID. Positive IDs are visible, negative IDs are hidden. */
|
||||
* Face Set ID. Positive IDs are visible, negative IDs are hidden.
|
||||
* The 0 ID is not used by the tools or the visibility system, it is just used when creating new
|
||||
* geometry (the trim tool, for example) to detect which geometry was just added, so it can be
|
||||
* assigned a valid Face Set after creation. Tools are not intended to run with Face Sets IDs set
|
||||
* to 0. */
|
||||
int *face_sets;
|
||||
|
||||
/* BMesh for dynamic topology sculpting */
|
||||
|
|
|
@ -48,10 +48,12 @@ using SplinePtr = std::unique_ptr<Spline>;
|
|||
* evaluation happens in a layer on top of the evaluated points generated by the derived types.
|
||||
*
|
||||
* There are a few methods to evaluate a spline:
|
||||
* 1. #evaluated_positions and #interpolate_to_evaluated_points give data at the initial
|
||||
* 1. #evaluated_positions and #interpolate_to_evaluated_points give data for the initial
|
||||
* evaluated points, depending on the resolution.
|
||||
* 2. #lookup_evaluated_factor and #lookup_evaluated_factor are meant for one-off lookups
|
||||
* along the length of a curve.
|
||||
* 3. #sample_uniform_index_factors returns an array that stores uniform-length samples
|
||||
* along the spline which can be used to interpolate data from method 1.
|
||||
*
|
||||
* Commonly used evaluated data is stored in caches on the spline itself so that operations on
|
||||
* splines don't need to worry about taking ownership of evaluated data when they don't need to.
|
||||
|
@ -64,11 +66,6 @@ class Spline {
|
|||
Poly,
|
||||
};
|
||||
|
||||
protected:
|
||||
Type type_;
|
||||
bool is_cyclic_ = false;
|
||||
|
||||
public:
|
||||
enum NormalCalculationMode {
|
||||
ZUp,
|
||||
Minimum,
|
||||
|
@ -78,6 +75,9 @@ class Spline {
|
|||
NormalCalculationMode normal_mode;
|
||||
|
||||
protected:
|
||||
Type type_;
|
||||
bool is_cyclic_ = false;
|
||||
|
||||
/** Direction of the spline at each evaluated point. */
|
||||
mutable blender::Vector<blender::float3> evaluated_tangents_cache_;
|
||||
mutable std::mutex tangent_cache_mutex_;
|
||||
|
@ -99,7 +99,7 @@ class Spline {
|
|||
{
|
||||
}
|
||||
Spline(Spline &other)
|
||||
: type_(other.type_), is_cyclic_(other.is_cyclic_), normal_mode(other.normal_mode)
|
||||
: normal_mode(other.normal_mode), type_(other.type_), is_cyclic_(other.is_cyclic_)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -206,8 +206,12 @@ class BezierSpline final : public Spline {
|
|||
blender::Vector<HandleType> handle_types_left_;
|
||||
blender::Vector<HandleType> handle_types_right_;
|
||||
|
||||
blender::Vector<blender::float3> handle_positions_left_;
|
||||
blender::Vector<blender::float3> handle_positions_right_;
|
||||
/* These are mutable to allow lazy recalculation of #Auto and #Vector handle positions. */
|
||||
mutable blender::Vector<blender::float3> handle_positions_left_;
|
||||
mutable blender::Vector<blender::float3> handle_positions_right_;
|
||||
|
||||
mutable std::mutex auto_handle_mutex_;
|
||||
mutable bool auto_handles_dirty_ = true;
|
||||
|
||||
/** Start index in evaluated points array for every control point. */
|
||||
mutable blender::Vector<int> offset_cache_;
|
||||
|
@ -286,7 +290,7 @@ class BezierSpline final : public Spline {
|
|||
int next_control_point_index;
|
||||
/**
|
||||
* Linear interpolation weight between the two indices, from 0 to 1.
|
||||
* Higher means next control point.
|
||||
* Higher means closer to next control point.
|
||||
*/
|
||||
float factor;
|
||||
};
|
||||
|
@ -296,6 +300,7 @@ class BezierSpline final : public Spline {
|
|||
const blender::fn::GVArray &source_data) const override;
|
||||
|
||||
private:
|
||||
void ensure_auto_handles() const;
|
||||
void correct_end_tangents() const final;
|
||||
bool segment_is_vector(const int start_index) const;
|
||||
void evaluate_bezier_segment(const int index,
|
||||
|
@ -316,6 +321,8 @@ class NURBSpline final : public Spline {
|
|||
EndPoint,
|
||||
Bezier,
|
||||
};
|
||||
|
||||
/** Method used to recalculate the knots vector when points are added or removed. */
|
||||
KnotsMode knots_mode;
|
||||
|
||||
struct BasisCache {
|
||||
|
@ -425,12 +432,10 @@ class NURBSpline final : public Spline {
|
|||
* points does not change it.
|
||||
*/
|
||||
class PolySpline final : public Spline {
|
||||
public:
|
||||
blender::Vector<blender::float3> positions_;
|
||||
blender::Vector<float> radii_;
|
||||
blender::Vector<float> tilts_;
|
||||
|
||||
private:
|
||||
public:
|
||||
SplinePtr copy() const final;
|
||||
PolySpline() : Spline(Type::Poly)
|
||||
|
@ -473,14 +478,24 @@ class PolySpline final : public Spline {
|
|||
* more of the data is stored in the splines, but also just to be different than the name in DNA.
|
||||
*/
|
||||
class CurveEval {
|
||||
private:
|
||||
blender::Vector<SplinePtr> splines_;
|
||||
|
||||
public:
|
||||
blender::Vector<SplinePtr> splines;
|
||||
blender::Span<SplinePtr> splines() const;
|
||||
blender::MutableSpan<SplinePtr> splines();
|
||||
|
||||
void add_spline(SplinePtr spline);
|
||||
void remove_splines(blender::IndexMask mask);
|
||||
|
||||
CurveEval *copy();
|
||||
|
||||
void translate(const blender::float3 &translation);
|
||||
void transform(const blender::float4x4 &matrix);
|
||||
void bounds_min_max(blender::float3 &min, blender::float3 &max, const bool use_evaluated) const;
|
||||
|
||||
blender::Array<int> control_point_offsets() const;
|
||||
blender::Array<int> evaluated_point_offsets() const;
|
||||
};
|
||||
|
||||
std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &curve);
|
||||
|
|
|
@ -947,7 +947,7 @@ static bool nlastrips_path_rename_fix(ID *owner_id,
|
|||
owner_id, prefix, oldName, newName, oldKey, newKey, &strip->act->curves, verify_paths);
|
||||
}
|
||||
/* Ignore own F-Curves, since those are local. */
|
||||
/* Check sub-strips (if metas) */
|
||||
/* Check sub-strips (if meta-strips). */
|
||||
is_changed |= nlastrips_path_rename_fix(
|
||||
owner_id, prefix, oldName, newName, oldKey, newKey, &strip->strips, verify_paths);
|
||||
}
|
||||
|
@ -1177,7 +1177,7 @@ static bool nlastrips_path_remove_fix(const char *prefix, ListBase *strips)
|
|||
any_removed |= fcurves_path_remove_fix(prefix, &strip->act->curves);
|
||||
}
|
||||
|
||||
/* check sub-strips (if metas) */
|
||||
/* Check sub-strips (if meta-strips). */
|
||||
any_removed |= nlastrips_path_remove_fix(prefix, &strip->strips);
|
||||
}
|
||||
return any_removed;
|
||||
|
@ -1245,7 +1245,7 @@ static void nlastrips_apply_all_curves_cb(ID *id, ListBase *strips, AllFCurvesCb
|
|||
fcurves_apply_cb(id, &strip->act->curves, wrapper->func, wrapper->user_data);
|
||||
}
|
||||
|
||||
/* check sub-strips (if metas) */
|
||||
/* Check sub-strips (if meta-strips). */
|
||||
nlastrips_apply_all_curves_cb(id, &strip->strips, wrapper);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1040,6 +1040,7 @@ static NlaEvalChannelSnapshot *nlaevalchan_snapshot_new(NlaEvalChannel *nec)
|
|||
nec_snapshot->channel = nec;
|
||||
nec_snapshot->length = length;
|
||||
nlavalidmask_init(&nec_snapshot->blend_domain, length);
|
||||
nlavalidmask_init(&nec_snapshot->remap_domain, length);
|
||||
|
||||
return nec_snapshot;
|
||||
}
|
||||
|
@ -1050,6 +1051,7 @@ static void nlaevalchan_snapshot_free(NlaEvalChannelSnapshot *nec_snapshot)
|
|||
BLI_assert(!nec_snapshot->is_base);
|
||||
|
||||
nlavalidmask_free(&nec_snapshot->blend_domain);
|
||||
nlavalidmask_free(&nec_snapshot->remap_domain);
|
||||
MEM_freeN(nec_snapshot);
|
||||
}
|
||||
|
||||
|
@ -1649,6 +1651,363 @@ static bool nla_combine_quaternion_get_inverted_strip_values(const float lower_v
|
|||
return true;
|
||||
}
|
||||
|
||||
/* ---------------------- */
|
||||
|
||||
/* Assert necs and necs->channel is nonNull. */
|
||||
static void nlaevalchan_assert_nonNull(NlaEvalChannelSnapshot *necs)
|
||||
{
|
||||
UNUSED_VARS_NDEBUG(necs);
|
||||
BLI_assert(necs != NULL && necs->channel != NULL);
|
||||
}
|
||||
|
||||
/* Assert that the channels given can be blended or combined together. */
|
||||
static void nlaevalchan_assert_blendOrcombine_compatible(NlaEvalChannelSnapshot *lower_necs,
|
||||
NlaEvalChannelSnapshot *upper_necs,
|
||||
NlaEvalChannelSnapshot *blended_necs)
|
||||
{
|
||||
UNUSED_VARS_NDEBUG(lower_necs, upper_necs, blended_necs);
|
||||
BLI_assert(!ELEM(NULL, lower_necs, blended_necs));
|
||||
BLI_assert(upper_necs == NULL || lower_necs->length == upper_necs->length);
|
||||
BLI_assert(lower_necs->length == blended_necs->length);
|
||||
}
|
||||
|
||||
/* Assert that the channels given can be blended or combined together as a quaternion. */
|
||||
static void nlaevalchan_assert_blendOrcombine_compatible_quaternion(
|
||||
NlaEvalChannelSnapshot *lower_necs,
|
||||
NlaEvalChannelSnapshot *upper_necs,
|
||||
NlaEvalChannelSnapshot *blended_necs)
|
||||
{
|
||||
nlaevalchan_assert_blendOrcombine_compatible(lower_necs, upper_necs, blended_necs);
|
||||
BLI_assert(lower_necs->length == 4);
|
||||
}
|
||||
|
||||
static void nlaevalchan_copy_values(NlaEvalChannelSnapshot *dst, NlaEvalChannelSnapshot *src)
|
||||
{
|
||||
memcpy(dst->values, src->values, src->length * sizeof(float));
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies lower necs to blended necs if upper necs is NULL or has zero influence.
|
||||
* \return true if copied.
|
||||
*/
|
||||
static bool nlaevalchan_blendOrcombine_try_copy_lower(NlaEvalChannelSnapshot *lower_necs,
|
||||
NlaEvalChannelSnapshot *upper_necs,
|
||||
const float upper_influence,
|
||||
NlaEvalChannelSnapshot *r_blended_necs)
|
||||
{
|
||||
const bool has_influence = !IS_EQF(upper_influence, 0.0f);
|
||||
if (upper_necs != NULL && has_influence) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nlaevalchan_copy_values(r_blended_necs, lower_necs);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on blend-mode, blend lower necs with upper necs into blended necs.
|
||||
*
|
||||
* Each upper value's blend domain determines whether to blend or to copy directly from lower.
|
||||
*/
|
||||
static void nlaevalchan_blend_value(NlaEvalChannelSnapshot *lower_necs,
|
||||
NlaEvalChannelSnapshot *upper_necs,
|
||||
const int upper_blendmode,
|
||||
const float upper_influence,
|
||||
NlaEvalChannelSnapshot *r_blended_necs)
|
||||
{
|
||||
nlaevalchan_assert_blendOrcombine_compatible(lower_necs, upper_necs, r_blended_necs);
|
||||
if (nlaevalchan_blendOrcombine_try_copy_lower(
|
||||
lower_necs, upper_necs, upper_influence, r_blended_necs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int length = lower_necs->length;
|
||||
for (int j = 0; j < length; j++) {
|
||||
if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, j)) {
|
||||
r_blended_necs->values[j] = lower_necs->values[j];
|
||||
continue;
|
||||
}
|
||||
|
||||
r_blended_necs->values[j] = nla_blend_value(
|
||||
upper_blendmode, lower_necs->values[j], upper_necs->values[j], upper_influence);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on mix-mode, provided by one the necs,
|
||||
* combines lower necs with upper necs into blended necs.
|
||||
*
|
||||
* Each upper value's blend domain determines whether to blend or to copy directly from lower.
|
||||
*/
|
||||
static void nlaevalchan_combine_value(NlaEvalChannelSnapshot *lower_necs,
|
||||
NlaEvalChannelSnapshot *upper_necs,
|
||||
const float upper_influence,
|
||||
NlaEvalChannelSnapshot *r_blended_necs)
|
||||
{
|
||||
nlaevalchan_assert_blendOrcombine_compatible(lower_necs, upper_necs, r_blended_necs);
|
||||
if (nlaevalchan_blendOrcombine_try_copy_lower(
|
||||
lower_necs, upper_necs, upper_influence, r_blended_necs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Assumes every base is the same. */
|
||||
float *base_values = lower_necs->channel->base_snapshot.values;
|
||||
const int length = lower_necs->length;
|
||||
const char mix_mode = lower_necs->channel->mix_mode;
|
||||
|
||||
for (int j = 0; j < length; j++) {
|
||||
if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, j)) {
|
||||
r_blended_necs->values[j] = lower_necs->values[j];
|
||||
continue;
|
||||
}
|
||||
|
||||
r_blended_necs->values[j] = nla_combine_value(
|
||||
mix_mode, base_values[j], lower_necs->values[j], upper_necs->values[j], upper_influence);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quaternion combines lower necs with upper necs into blended necs.
|
||||
*
|
||||
* Each upper value's blend domain determines whether to blend or to copy directly
|
||||
* from lower.
|
||||
*/
|
||||
static void nlaevalchan_combine_quaternion(NlaEvalChannelSnapshot *lower_necs,
|
||||
NlaEvalChannelSnapshot *upper_necs,
|
||||
const float upper_influence,
|
||||
NlaEvalChannelSnapshot *r_blended_necs)
|
||||
{
|
||||
nlaevalchan_assert_blendOrcombine_compatible_quaternion(lower_necs, upper_necs, r_blended_necs);
|
||||
if (nlaevalchan_blendOrcombine_try_copy_lower(
|
||||
lower_necs, upper_necs, upper_influence, r_blended_necs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** No need to check per index. We limit to all or nothing combining for quaternions. */
|
||||
if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, 0)) {
|
||||
nlaevalchan_copy_values(r_blended_necs, lower_necs);
|
||||
return;
|
||||
}
|
||||
|
||||
nla_combine_quaternion(
|
||||
lower_necs->values, upper_necs->values, upper_influence, r_blended_necs->values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on blend-mode and mix-mode, blend lower necs with upper necs into blended necs.
|
||||
*
|
||||
* Each upper value's blend domain determines whether to blend or to copy directly
|
||||
* from lower.
|
||||
*
|
||||
* \param lower_necs: Never NULL.
|
||||
* \param upper_necs: Can be NULL.
|
||||
* \param upper_blendmode: Enum value in eNlaStrip_Blend_Mode.
|
||||
* \param upper_influence: Value in range [0, 1].
|
||||
* \param upper_necs: Never NULL.
|
||||
*
|
||||
*/
|
||||
static void nlaevalchan_blendOrcombine(NlaEvalChannelSnapshot *lower_necs,
|
||||
NlaEvalChannelSnapshot *upper_necs,
|
||||
const int upper_blendmode,
|
||||
const float upper_influence,
|
||||
NlaEvalChannelSnapshot *r_blended_necs)
|
||||
{
|
||||
nlaevalchan_assert_nonNull(r_blended_necs);
|
||||
|
||||
switch (upper_blendmode) {
|
||||
case NLASTRIP_MODE_COMBINE: {
|
||||
switch (r_blended_necs->channel->mix_mode) {
|
||||
case NEC_MIX_QUATERNION: {
|
||||
nlaevalchan_combine_quaternion(lower_necs, upper_necs, upper_influence, r_blended_necs);
|
||||
return;
|
||||
}
|
||||
case NEC_MIX_ADD:
|
||||
case NEC_MIX_AXIS_ANGLE:
|
||||
case NEC_MIX_MULTIPLY: {
|
||||
nlaevalchan_combine_value(lower_necs, upper_necs, upper_influence, r_blended_necs);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
BLI_assert("Mix mode should've been handled");
|
||||
}
|
||||
return;
|
||||
}
|
||||
case NLASTRIP_MODE_ADD:
|
||||
case NLASTRIP_MODE_SUBTRACT:
|
||||
case NLASTRIP_MODE_MULTIPLY:
|
||||
case NLASTRIP_MODE_REPLACE: {
|
||||
nlaevalchan_blend_value(
|
||||
lower_necs, upper_necs, upper_blendmode, upper_influence, r_blended_necs);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
BLI_assert("Blend mode should've been handled");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on blend-mode, solve for the upper values such that when lower blended with upper then we
|
||||
* get blended values as a result.
|
||||
*
|
||||
* Only processes blended values in the remap domain. Successfully remapped upper values are placed
|
||||
* in the remap domain so caller knows which values are usable.
|
||||
*/
|
||||
static void nlaevalchan_blend_value_get_inverted_upper_evalchan(
|
||||
NlaEvalChannelSnapshot *lower_necs,
|
||||
NlaEvalChannelSnapshot *blended_necs,
|
||||
const int upper_blendmode,
|
||||
const float upper_influence,
|
||||
NlaEvalChannelSnapshot *r_upper_necs)
|
||||
{
|
||||
nlaevalchan_assert_nonNull(r_upper_necs);
|
||||
nlaevalchan_assert_blendOrcombine_compatible(lower_necs, r_upper_necs, blended_necs);
|
||||
|
||||
const int length = lower_necs->length;
|
||||
for (int j = 0; j < length; j++) {
|
||||
if (!BLI_BITMAP_TEST_BOOL(blended_necs->remap_domain.ptr, j)) {
|
||||
BLI_BITMAP_DISABLE(r_upper_necs->remap_domain.ptr, j);
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool success = nla_blend_get_inverted_strip_value(upper_blendmode,
|
||||
lower_necs->values[j],
|
||||
blended_necs->values[j],
|
||||
upper_influence,
|
||||
&r_upper_necs->values[j]);
|
||||
BLI_BITMAP_SET(r_upper_necs->remap_domain.ptr, j, success);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on mix-mode, solve for the upper values such that when lower combined with upper then we
|
||||
* get blended values as a result.
|
||||
*
|
||||
* Only processes blended values in the remap domain. Successfully remapped upper values are placed
|
||||
* in the remap domain so caller knows which values are usable.
|
||||
*/
|
||||
static void nlaevalchan_combine_value_get_inverted_upper_evalchan(
|
||||
NlaEvalChannelSnapshot *lower_necs,
|
||||
NlaEvalChannelSnapshot *blended_necs,
|
||||
const float upper_influence,
|
||||
NlaEvalChannelSnapshot *r_upper_necs)
|
||||
{
|
||||
nlaevalchan_assert_nonNull(r_upper_necs);
|
||||
nlaevalchan_assert_blendOrcombine_compatible(lower_necs, r_upper_necs, blended_necs);
|
||||
|
||||
/* Assumes every channel's base is the same. */
|
||||
float *base_values = lower_necs->channel->base_snapshot.values;
|
||||
const int length = lower_necs->length;
|
||||
const char mix_mode = lower_necs->channel->mix_mode;
|
||||
|
||||
for (int j = 0; j < length; j++) {
|
||||
if (!BLI_BITMAP_TEST_BOOL(blended_necs->remap_domain.ptr, j)) {
|
||||
BLI_BITMAP_DISABLE(r_upper_necs->remap_domain.ptr, j);
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool success = nla_combine_get_inverted_strip_value(mix_mode,
|
||||
base_values[j],
|
||||
lower_necs->values[j],
|
||||
blended_necs->values[j],
|
||||
upper_influence,
|
||||
&r_upper_necs->values[j]);
|
||||
|
||||
BLI_BITMAP_SET(r_upper_necs->remap_domain.ptr, j, success);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Solve for the upper values such that when lower quaternion combined with upper then we get
|
||||
* blended values as a result.
|
||||
*
|
||||
* All blended values must be in the remap domain. If successfully remapped, then all upper values
|
||||
* are placed in the remap domain so caller knows the result is usable.
|
||||
*/
|
||||
static void nlaevalchan_combine_quaternion_get_inverted_upper_evalchan(
|
||||
NlaEvalChannelSnapshot *lower_necs,
|
||||
NlaEvalChannelSnapshot *blended_necs,
|
||||
const float upper_influence,
|
||||
NlaEvalChannelSnapshot *r_upper_necs)
|
||||
{
|
||||
nlaevalchan_assert_nonNull(r_upper_necs);
|
||||
nlaevalchan_assert_blendOrcombine_compatible_quaternion(lower_necs, r_upper_necs, blended_necs);
|
||||
|
||||
/* Must check each domain index individually in case animator had a non-combine NLA strip with a
|
||||
* subset of quaternion channels and remapping through any of them failed and thus potentially
|
||||
* has undefined values. */
|
||||
for (int j = 0; j < 4; j++) {
|
||||
if (!BLI_BITMAP_TEST_BOOL(blended_necs->remap_domain.ptr, j)) {
|
||||
BLI_bitmap_set_all(r_upper_necs->remap_domain.ptr, false, 4);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const bool success = nla_combine_quaternion_get_inverted_strip_values(
|
||||
lower_necs->values, blended_necs->values, upper_influence, r_upper_necs->values);
|
||||
|
||||
BLI_bitmap_set_all(r_upper_necs->remap_domain.ptr, success, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on blend-mode and mix mode, solve for the upper values such that when lower blended or
|
||||
* combined with upper then we get blended values as a result.
|
||||
*
|
||||
* Only processes blended values in the remap domain. Successfully remapped upper values are placed
|
||||
* in the remap domain so caller knows which values are usable.
|
||||
*
|
||||
* \param lower_necs: Never NULL.
|
||||
* \param blended_necs: Never NULL.
|
||||
* \param upper_blendmode: Enum value in eNlaStrip_Blend_Mode.
|
||||
* \param upper_influence: Value in range [0, 1].
|
||||
* \param r_upper_necs: Never NULL.
|
||||
*/
|
||||
static void nlaevalchan_blendOrcombine_get_inverted_upper_evalchan(
|
||||
NlaEvalChannelSnapshot *lower_necs,
|
||||
NlaEvalChannelSnapshot *blended_necs,
|
||||
const int upper_blendmode,
|
||||
const float upper_influence,
|
||||
NlaEvalChannelSnapshot *r_upper_necs)
|
||||
{
|
||||
nlaevalchan_assert_nonNull(r_upper_necs);
|
||||
|
||||
if (IS_EQF(upper_influence, 0.0f)) {
|
||||
BLI_bitmap_set_all(r_upper_necs->remap_domain.ptr, false, r_upper_necs->length);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (upper_blendmode) {
|
||||
case NLASTRIP_MODE_COMBINE: {
|
||||
switch (r_upper_necs->channel->mix_mode) {
|
||||
case NEC_MIX_QUATERNION: {
|
||||
nlaevalchan_combine_quaternion_get_inverted_upper_evalchan(
|
||||
lower_necs, blended_necs, upper_influence, r_upper_necs);
|
||||
return;
|
||||
}
|
||||
case NEC_MIX_ADD:
|
||||
case NEC_MIX_AXIS_ANGLE:
|
||||
case NEC_MIX_MULTIPLY: {
|
||||
nlaevalchan_combine_value_get_inverted_upper_evalchan(
|
||||
lower_necs, blended_necs, upper_influence, r_upper_necs);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
BLI_assert("Mix mode should've been handled");
|
||||
}
|
||||
return;
|
||||
}
|
||||
case NLASTRIP_MODE_ADD:
|
||||
case NLASTRIP_MODE_SUBTRACT:
|
||||
case NLASTRIP_MODE_MULTIPLY:
|
||||
case NLASTRIP_MODE_REPLACE: {
|
||||
nlaevalchan_blend_value_get_inverted_upper_evalchan(
|
||||
lower_necs, blended_necs, upper_blendmode, upper_influence, r_upper_necs);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
BLI_assert("Blend mode should've been handled");
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------- */
|
||||
/* F-Modifier stack joining/separation utilities -
|
||||
* should we generalize these for BLI_listbase.h interface? */
|
||||
|
@ -2048,12 +2407,12 @@ static void nla_eval_domain_strips(PointerRNA *ptr,
|
|||
GSet *touched_actions)
|
||||
{
|
||||
LISTBASE_FOREACH (NlaStrip *, strip, strips) {
|
||||
/* check strip's action */
|
||||
/* Check strip's action. */
|
||||
if (strip->act) {
|
||||
nla_eval_domain_action(ptr, channels, strip->act, touched_actions);
|
||||
}
|
||||
|
||||
/* check sub-strips (if metas) */
|
||||
/* Check sub-strips (if meta-strips). */
|
||||
nla_eval_domain_strips(ptr, channels, &strip->strips, touched_actions);
|
||||
}
|
||||
}
|
||||
|
@ -2500,9 +2859,9 @@ void nlasnapshot_ensure_channels(NlaEvalData *eval_data, NlaEvalSnapshot *snapsh
|
|||
* Blends the \a lower_snapshot with the \a upper_snapshot into \a r_blended_snapshot according
|
||||
* to the given \a upper_blendmode and \a upper_influence.
|
||||
*
|
||||
* For \a upper_snapshot, blending limited to values in the \a blend_domain. For Replace blendmode,
|
||||
* this allows the upper snapshot to have a location XYZ channel where only a subset of values are
|
||||
* blended.
|
||||
* For \a upper_snapshot, blending limited to values in the \a blend_domain.
|
||||
* For Replace blend-mode, this allows the upper snapshot to have a location XYZ channel
|
||||
* where only a subset of values are blended.
|
||||
*/
|
||||
void nlasnapshot_blend(NlaEvalData *eval_data,
|
||||
NlaEvalSnapshot *lower_snapshot,
|
||||
|
@ -2513,11 +2872,7 @@ void nlasnapshot_blend(NlaEvalData *eval_data,
|
|||
{
|
||||
nlaeval_snapshot_ensure_size(r_blended_snapshot, eval_data->num_channels);
|
||||
|
||||
const bool zero_upper_influence = IS_EQF(upper_influence, 0.0f);
|
||||
|
||||
LISTBASE_FOREACH (NlaEvalChannel *, nec, &eval_data->channels) {
|
||||
const int length = nec->base_snapshot.length;
|
||||
|
||||
NlaEvalChannelSnapshot *upper_necs = nlaeval_snapshot_get(upper_snapshot, nec->index);
|
||||
NlaEvalChannelSnapshot *lower_necs = nlaeval_snapshot_get(lower_snapshot, nec->index);
|
||||
if (upper_necs == NULL && lower_necs == NULL) {
|
||||
|
@ -2530,49 +2885,44 @@ void nlasnapshot_blend(NlaEvalData *eval_data,
|
|||
}
|
||||
|
||||
NlaEvalChannelSnapshot *result_necs = nlaeval_snapshot_ensure_channel(r_blended_snapshot, nec);
|
||||
nlaevalchan_blendOrcombine(
|
||||
lower_necs, upper_necs, upper_blendmode, upper_influence, result_necs);
|
||||
}
|
||||
}
|
||||
|
||||
/** Always copy \a lower_snapshot to result, irrelevant of whether \a upper_snapshot has a
|
||||
* corresponding channel. This only matters when \a lower_snapshot not the same as
|
||||
* \a r_blended_snapshot. */
|
||||
memcpy(result_necs->values, lower_necs->values, length * sizeof(float));
|
||||
if (upper_necs == NULL || zero_upper_influence) {
|
||||
/**
|
||||
* Using \a blended_snapshot and \a lower_snapshot, we can solve for the \a r_upper_snapshot.
|
||||
*
|
||||
* Only channels that exist within \a blended_snapshot are inverted.
|
||||
*
|
||||
* For \a r_upper_snapshot, disables \a NlaEvalChannelSnapshot->remap_domain for failed inversions.
|
||||
* Only values within the \a remap_domain are processed.
|
||||
*/
|
||||
void nlasnapshot_blend_get_inverted_upper_snapshot(NlaEvalData *eval_data,
|
||||
NlaEvalSnapshot *lower_snapshot,
|
||||
NlaEvalSnapshot *blended_snapshot,
|
||||
const short upper_blendmode,
|
||||
const float upper_influence,
|
||||
NlaEvalSnapshot *r_upper_snapshot)
|
||||
{
|
||||
nlaeval_snapshot_ensure_size(r_upper_snapshot, eval_data->num_channels);
|
||||
|
||||
LISTBASE_FOREACH (NlaEvalChannel *, nec, &eval_data->channels) {
|
||||
NlaEvalChannelSnapshot *blended_necs = nlaeval_snapshot_get(blended_snapshot, nec->index);
|
||||
if (blended_necs == NULL) {
|
||||
/** We assume the caller only wants a subset of channels to be inverted, those that exist
|
||||
* within \a blended_snapshot. */
|
||||
continue;
|
||||
}
|
||||
|
||||
if (upper_blendmode == NLASTRIP_MODE_COMBINE) {
|
||||
const int mix_mode = nec->mix_mode;
|
||||
if (mix_mode == NEC_MIX_QUATERNION) {
|
||||
if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nla_combine_quaternion(
|
||||
lower_necs->values, upper_necs->values, upper_influence, result_necs->values);
|
||||
}
|
||||
else {
|
||||
for (int j = 0; j < length; j++) {
|
||||
if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, j)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result_necs->values[j] = nla_combine_value(mix_mode,
|
||||
nec->base_snapshot.values[j],
|
||||
lower_necs->values[j],
|
||||
upper_necs->values[j],
|
||||
upper_influence);
|
||||
}
|
||||
}
|
||||
NlaEvalChannelSnapshot *lower_necs = nlaeval_snapshot_get(lower_snapshot, nec->index);
|
||||
if (lower_necs == NULL) {
|
||||
lower_necs = nlaeval_snapshot_find_channel(lower_snapshot->base, nec);
|
||||
}
|
||||
else {
|
||||
for (int j = 0; j < length; j++) {
|
||||
if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, j)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result_necs->values[j] = nla_blend_value(
|
||||
upper_blendmode, lower_necs->values[j], upper_necs->values[j], upper_influence);
|
||||
}
|
||||
}
|
||||
NlaEvalChannelSnapshot *result_necs = nlaeval_snapshot_ensure_channel(r_upper_snapshot, nec);
|
||||
nlaevalchan_blendOrcombine_get_inverted_upper_evalchan(
|
||||
lower_necs, blended_necs, upper_blendmode, upper_influence, result_necs);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2670,74 +3020,64 @@ bool BKE_animsys_nla_remap_keyframe_values(struct NlaKeyframingContext *context,
|
|||
return false;
|
||||
}
|
||||
|
||||
/* Find the evaluation channel for the NLA stack below current strip. */
|
||||
/** Create \a blended_snapshot and fill with input \a values. */
|
||||
NlaEvalData *eval_data = &context->lower_eval_data;
|
||||
NlaEvalSnapshot blended_snapshot;
|
||||
nlaeval_snapshot_init(&blended_snapshot, eval_data, NULL);
|
||||
|
||||
NlaEvalChannelKey key = {
|
||||
.ptr = *prop_ptr,
|
||||
.prop = prop,
|
||||
};
|
||||
/**
|
||||
* Remove lower NLA stack effects.
|
||||
*
|
||||
* Using the tweak strip's blended result and the lower snapshot value, we can solve for the
|
||||
* tweak strip value it must evaluate to.
|
||||
*/
|
||||
NlaEvalData *const lower_eval_data = &context->lower_eval_data;
|
||||
NlaEvalChannel *const lower_nec = nlaevalchan_verify_key(lower_eval_data, NULL, &key);
|
||||
|
||||
if ((lower_nec->base_snapshot.length != count)) {
|
||||
NlaEvalChannel *nec = nlaevalchan_verify_key(eval_data, NULL, &key);
|
||||
BLI_assert(nec);
|
||||
if (nec->base_snapshot.length != count) {
|
||||
BLI_assert(!"invalid value count");
|
||||
nlaeval_snapshot_free_data(&blended_snapshot);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Invert the blending operation to compute the desired strip values. */
|
||||
NlaEvalChannelSnapshot *const lower_nec_snapshot = nlaeval_snapshot_find_channel(
|
||||
&lower_eval_data->eval_snapshot, lower_nec);
|
||||
NlaEvalChannelSnapshot *blended_necs = nlaeval_snapshot_ensure_channel(&blended_snapshot, nec);
|
||||
memcpy(blended_necs->values, values, sizeof(float) * count);
|
||||
BLI_bitmap_set_all(blended_necs->remap_domain.ptr, true, count);
|
||||
|
||||
float *lower_values = lower_nec_snapshot->values;
|
||||
/** Remove lower NLA stack effects. */
|
||||
nlasnapshot_blend_get_inverted_upper_snapshot(eval_data,
|
||||
&context->lower_eval_data.eval_snapshot,
|
||||
&blended_snapshot,
|
||||
blend_mode,
|
||||
influence,
|
||||
&blended_snapshot);
|
||||
|
||||
if (blend_mode == NLASTRIP_MODE_COMBINE) {
|
||||
/* Quaternion combine handles all sub-channels as a unit. */
|
||||
if (lower_nec->mix_mode == NEC_MIX_QUATERNION) {
|
||||
if (r_force_all == NULL) {
|
||||
return false;
|
||||
}
|
||||
/** Write results into \a values. */
|
||||
bool successful_remap = true;
|
||||
if (blended_necs->channel->mix_mode == NEC_MIX_QUATERNION &&
|
||||
blend_mode == NLASTRIP_MODE_COMBINE) {
|
||||
|
||||
if (r_force_all != NULL) {
|
||||
*r_force_all = true;
|
||||
|
||||
if (!nla_combine_quaternion_get_inverted_strip_values(
|
||||
lower_values, values, influence, values)) {
|
||||
return false;
|
||||
}
|
||||
index = -1;
|
||||
}
|
||||
else {
|
||||
float *base_values = lower_nec->base_snapshot.values;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (ELEM(index, i, -1)) {
|
||||
if (!nla_combine_get_inverted_strip_value(lower_nec->mix_mode,
|
||||
base_values[i],
|
||||
lower_values[i],
|
||||
values[i],
|
||||
influence,
|
||||
&values[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (ELEM(index, i, -1)) {
|
||||
if (!nla_blend_get_inverted_strip_value(
|
||||
blend_mode, lower_values[i], values[i], influence, &values[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
successful_remap = false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (!ELEM(index, i, -1)) {
|
||||
continue;
|
||||
}
|
||||
if (!BLI_BITMAP_TEST_BOOL(blended_necs->remap_domain.ptr, i)) {
|
||||
successful_remap = false;
|
||||
}
|
||||
|
||||
values[i] = blended_necs->values[i];
|
||||
}
|
||||
|
||||
nlaeval_snapshot_free_data(&blended_snapshot);
|
||||
|
||||
return successful_remap;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -23,16 +23,39 @@
|
|||
#include "BKE_curve.h"
|
||||
#include "BKE_spline.hh"
|
||||
|
||||
using blender::Array;
|
||||
using blender::float3;
|
||||
using blender::float4x4;
|
||||
using blender::Span;
|
||||
|
||||
blender::Span<SplinePtr> CurveEval::splines() const
|
||||
{
|
||||
return splines_;
|
||||
}
|
||||
|
||||
blender::MutableSpan<SplinePtr> CurveEval::splines()
|
||||
{
|
||||
return splines_;
|
||||
}
|
||||
|
||||
void CurveEval::add_spline(SplinePtr spline)
|
||||
{
|
||||
splines_.append(std::move(spline));
|
||||
}
|
||||
|
||||
void CurveEval::remove_splines(blender::IndexMask mask)
|
||||
{
|
||||
for (int i = mask.size() - 1; i >= 0; i--) {
|
||||
splines_.remove_and_reorder(mask.indices()[i]);
|
||||
}
|
||||
}
|
||||
|
||||
CurveEval *CurveEval::copy()
|
||||
{
|
||||
CurveEval *new_curve = new CurveEval();
|
||||
|
||||
for (SplinePtr &spline : this->splines) {
|
||||
new_curve->splines.append(spline->copy());
|
||||
for (SplinePtr &spline : this->splines()) {
|
||||
new_curve->add_spline(spline->copy());
|
||||
}
|
||||
|
||||
return new_curve;
|
||||
|
@ -40,7 +63,7 @@ CurveEval *CurveEval::copy()
|
|||
|
||||
void CurveEval::translate(const float3 &translation)
|
||||
{
|
||||
for (SplinePtr &spline : this->splines) {
|
||||
for (SplinePtr &spline : this->splines()) {
|
||||
spline->translate(translation);
|
||||
spline->mark_cache_invalid();
|
||||
}
|
||||
|
@ -48,18 +71,52 @@ void CurveEval::translate(const float3 &translation)
|
|||
|
||||
void CurveEval::transform(const float4x4 &matrix)
|
||||
{
|
||||
for (SplinePtr &spline : this->splines) {
|
||||
for (SplinePtr &spline : this->splines()) {
|
||||
spline->transform(matrix);
|
||||
}
|
||||
}
|
||||
|
||||
void CurveEval::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) const
|
||||
{
|
||||
for (const SplinePtr &spline : this->splines) {
|
||||
for (const SplinePtr &spline : this->splines()) {
|
||||
spline->bounds_min_max(min, max, use_evaluated);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the start indices for each of the curve spline's evaluated points, as if they were part
|
||||
* of a flattened array. This can be used to facilitate parallelism by avoiding the need to
|
||||
* accumulate an offset while doing more complex calculations.
|
||||
*
|
||||
* \note The result array is one longer than the spline count; the last element is the total size.
|
||||
*/
|
||||
blender::Array<int> CurveEval::control_point_offsets() const
|
||||
{
|
||||
Array<int> offsets(splines_.size() + 1);
|
||||
int offset = 0;
|
||||
for (const int i : splines_.index_range()) {
|
||||
offsets[i] = offset;
|
||||
offset += splines_[i]->size();
|
||||
}
|
||||
offsets.last() = offset;
|
||||
return offsets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exactly like #control_point_offsets, but uses the number of evaluated points instead.
|
||||
*/
|
||||
blender::Array<int> CurveEval::evaluated_point_offsets() const
|
||||
{
|
||||
Array<int> offsets(splines_.size() + 1);
|
||||
int offset = 0;
|
||||
for (const int i : splines_.index_range()) {
|
||||
offsets[i] = offset;
|
||||
offset += splines_[i]->evaluated_points_size();
|
||||
}
|
||||
offsets.last() = offset;
|
||||
return offsets;
|
||||
}
|
||||
|
||||
static BezierSpline::HandleType handle_type_from_dna_bezt(const eBezTriple_Handle dna_handle_type)
|
||||
{
|
||||
switch (dna_handle_type) {
|
||||
|
@ -115,8 +172,6 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve)
|
|||
|
||||
const ListBase *nurbs = BKE_curve_nurbs_get(&const_cast<Curve &>(dna_curve));
|
||||
|
||||
curve->splines.reserve(BLI_listbase_count(nurbs));
|
||||
|
||||
/* TODO: Optimize by reserving the correct points size. */
|
||||
LISTBASE_FOREACH (const Nurb *, nurb, nurbs) {
|
||||
switch (nurb->type) {
|
||||
|
@ -135,7 +190,7 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve)
|
|||
bezt.tilt);
|
||||
}
|
||||
|
||||
curve->splines.append(std::move(spline));
|
||||
curve->add_spline(std::move(spline));
|
||||
break;
|
||||
}
|
||||
case CU_NURBS: {
|
||||
|
@ -149,7 +204,7 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve)
|
|||
spline->add_point(bp.vec, bp.radius, bp.tilt, bp.vec[3]);
|
||||
}
|
||||
|
||||
curve->splines.append(std::move(spline));
|
||||
curve->add_spline(std::move(spline));
|
||||
break;
|
||||
}
|
||||
case CU_POLY: {
|
||||
|
@ -160,7 +215,7 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve)
|
|||
spline->add_point(bp.vec, bp.radius, bp.tilt);
|
||||
}
|
||||
|
||||
curve->splines.append(std::move(spline));
|
||||
curve->add_spline(std::move(spline));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
@ -174,11 +229,9 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve)
|
|||
* from multiple curve objects, where the value may be different. */
|
||||
const Spline::NormalCalculationMode normal_mode = normal_mode_from_dna_curve(
|
||||
dna_curve.twist_mode);
|
||||
for (SplinePtr &spline : curve->splines) {
|
||||
for (SplinePtr &spline : curve->splines()) {
|
||||
spline->normal_mode = normal_mode;
|
||||
}
|
||||
|
||||
return curve;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -125,13 +125,13 @@ int CurveComponent::attribute_domain_size(const AttributeDomain domain) const
|
|||
}
|
||||
if (domain == ATTR_DOMAIN_POINT) {
|
||||
int total = 0;
|
||||
for (const SplinePtr &spline : curve_->splines) {
|
||||
for (const SplinePtr &spline : curve_->splines()) {
|
||||
total += spline->size();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
if (domain == ATTR_DOMAIN_CURVE) {
|
||||
return curve_->splines.size();
|
||||
return curve_->splines().size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -245,7 +245,7 @@ static void set_spline_resolution(SplinePtr &spline, const int resolution)
|
|||
static GVArrayPtr make_resolution_read_attribute(const CurveEval &curve)
|
||||
{
|
||||
return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, int, get_spline_resolution>>(
|
||||
curve.splines.as_span());
|
||||
curve.splines());
|
||||
}
|
||||
|
||||
static GVMutableArrayPtr make_resolution_write_attribute(CurveEval &curve)
|
||||
|
@ -254,7 +254,7 @@ static GVMutableArrayPtr make_resolution_write_attribute(CurveEval &curve)
|
|||
int,
|
||||
get_spline_resolution,
|
||||
set_spline_resolution>>(
|
||||
curve.splines.as_mutable_span());
|
||||
curve.splines());
|
||||
}
|
||||
|
||||
static bool get_cyclic_value(const SplinePtr &spline)
|
||||
|
@ -273,25 +273,422 @@ static void set_cyclic_value(SplinePtr &spline, const bool value)
|
|||
static GVArrayPtr make_cyclic_read_attribute(const CurveEval &curve)
|
||||
{
|
||||
return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, bool, get_cyclic_value>>(
|
||||
curve.splines.as_span());
|
||||
curve.splines());
|
||||
}
|
||||
|
||||
static GVMutableArrayPtr make_cyclic_write_attribute(CurveEval &curve)
|
||||
{
|
||||
return std::make_unique<
|
||||
fn::GVMutableArray_For_DerivedSpan<SplinePtr, bool, get_cyclic_value, set_cyclic_value>>(
|
||||
curve.splines.as_mutable_span());
|
||||
curve.splines());
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Builtin Control Point Attributes
|
||||
*
|
||||
* Attributes with a value for every control point. Most of the complexity here is due to the fact
|
||||
* that we must provide access to the attribute data as if it was a contiguous array when it is
|
||||
* really stored separately on each spline. That will be inherently rather slow, but these virtual
|
||||
* array implementations try to make it workable in common situations.
|
||||
* \{ */
|
||||
|
||||
namespace {
|
||||
struct PointIndices {
|
||||
int spline_index;
|
||||
int point_index;
|
||||
};
|
||||
} // namespace
|
||||
static PointIndices lookup_point_indices(Span<int> offsets, const int index)
|
||||
{
|
||||
const int spline_index = std::upper_bound(offsets.begin(), offsets.end(), index) -
|
||||
offsets.begin() - 1;
|
||||
const int index_in_spline = index - offsets[spline_index];
|
||||
return {spline_index, index_in_spline};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void point_attribute_materialize(Span<Span<T>> data,
|
||||
Span<int> offsets,
|
||||
const IndexMask mask,
|
||||
MutableSpan<T> r_span)
|
||||
{
|
||||
const int total_size = offsets.last();
|
||||
if (mask.is_range() && mask.as_range() == IndexRange(total_size)) {
|
||||
for (const int spline_index : data.index_range()) {
|
||||
const int offset = offsets[spline_index];
|
||||
const int next_offset = offsets[spline_index + 1];
|
||||
initialized_copy_n(data[spline_index].data(), next_offset - offset, r_span.data() + offset);
|
||||
}
|
||||
}
|
||||
else {
|
||||
int spline_index = 0;
|
||||
for (const int i : r_span.index_range()) {
|
||||
const int dst_index = mask[i];
|
||||
|
||||
while (offsets[spline_index] < dst_index) {
|
||||
spline_index++;
|
||||
}
|
||||
|
||||
const int index_in_spline = dst_index - offsets[spline_index];
|
||||
r_span[dst_index] = data[spline_index][index_in_spline];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void point_attribute_materialize_to_uninitialized(Span<Span<T>> data,
|
||||
Span<int> offsets,
|
||||
const IndexMask mask,
|
||||
MutableSpan<T> r_span)
|
||||
{
|
||||
T *dst = r_span.data();
|
||||
const int total_size = offsets.last();
|
||||
if (mask.is_range() && mask.as_range() == IndexRange(total_size)) {
|
||||
for (const int spline_index : data.index_range()) {
|
||||
const int offset = offsets[spline_index];
|
||||
const int next_offset = offsets[spline_index + 1];
|
||||
uninitialized_copy_n(data[spline_index].data(), next_offset - offset, dst + offset);
|
||||
}
|
||||
}
|
||||
else {
|
||||
int spline_index = 0;
|
||||
for (const int i : r_span.index_range()) {
|
||||
const int dst_index = mask[i];
|
||||
|
||||
while (offsets[spline_index] < dst_index) {
|
||||
spline_index++;
|
||||
}
|
||||
|
||||
const int index_in_spline = dst_index - offsets[spline_index];
|
||||
new (dst + dst_index) T(data[spline_index][index_in_spline]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual array for any control point data accessed with spans and an offset array.
|
||||
*/
|
||||
template<typename T> class VArray_For_SplinePoints : public VArray<T> {
|
||||
private:
|
||||
const Array<Span<T>> data_;
|
||||
Array<int> offsets_;
|
||||
|
||||
public:
|
||||
VArray_For_SplinePoints(Array<Span<T>> data, Array<int> offsets)
|
||||
: VArray<T>(offsets.last()), data_(std::move(data)), offsets_(std::move(offsets))
|
||||
{
|
||||
}
|
||||
|
||||
T get_impl(const int64_t index) const final
|
||||
{
|
||||
const PointIndices indices = lookup_point_indices(offsets_, index);
|
||||
return data_[indices.spline_index][indices.point_index];
|
||||
}
|
||||
|
||||
void materialize_impl(const IndexMask mask, MutableSpan<T> r_span) const final
|
||||
{
|
||||
point_attribute_materialize(data_.as_span(), offsets_, mask, r_span);
|
||||
}
|
||||
|
||||
void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan<T> r_span) const final
|
||||
{
|
||||
point_attribute_materialize_to_uninitialized(data_.as_span(), offsets_, mask, r_span);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutable virtual array for any control point data accessed with spans and an offset array.
|
||||
*/
|
||||
template<typename T> class VMutableArray_For_SplinePoints final : public VMutableArray<T> {
|
||||
private:
|
||||
Array<MutableSpan<T>> data_;
|
||||
Array<int> offsets_;
|
||||
|
||||
public:
|
||||
VMutableArray_For_SplinePoints(Array<MutableSpan<T>> data, Array<int> offsets)
|
||||
: VMutableArray<T>(offsets.last()), data_(std::move(data)), offsets_(std::move(offsets))
|
||||
{
|
||||
}
|
||||
|
||||
T get_impl(const int64_t index) const final
|
||||
{
|
||||
const PointIndices indices = lookup_point_indices(offsets_, index);
|
||||
return data_[indices.spline_index][indices.point_index];
|
||||
}
|
||||
|
||||
void set_impl(const int64_t index, T value) final
|
||||
{
|
||||
const PointIndices indices = lookup_point_indices(offsets_, index);
|
||||
data_[indices.spline_index][indices.point_index] = value;
|
||||
}
|
||||
|
||||
void set_all_impl(Span<T> src) final
|
||||
{
|
||||
for (const int spline_index : data_.index_range()) {
|
||||
const int offset = offsets_[spline_index];
|
||||
const int next_offsets = offsets_[spline_index + 1];
|
||||
data_[spline_index].copy_from(src.slice(offset, next_offsets - offset));
|
||||
}
|
||||
}
|
||||
|
||||
void materialize_impl(const IndexMask mask, MutableSpan<T> r_span) const final
|
||||
{
|
||||
point_attribute_materialize({(Span<T> *)data_.data(), data_.size()}, offsets_, mask, r_span);
|
||||
}
|
||||
|
||||
void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan<T> r_span) const final
|
||||
{
|
||||
point_attribute_materialize_to_uninitialized(
|
||||
{(Span<T> *)data_.data(), data_.size()}, offsets_, mask, r_span);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Virtual array implementation specifically for control point positions. This is only needed for
|
||||
* Bezier splines, where adjusting the position also requires adjusting handle positions depending
|
||||
* on handle types. We pay a small price for this when other spline types are mixed with Bezier.
|
||||
*
|
||||
* \note There is no need to check the handle type to avoid changing auto handles, since
|
||||
* retrieving write access to the position data will mark them for recomputation anyway.
|
||||
*/
|
||||
class VMutableArray_For_SplinePosition final : public VMutableArray<float3> {
|
||||
private:
|
||||
MutableSpan<SplinePtr> splines_;
|
||||
Array<int> offsets_;
|
||||
|
||||
public:
|
||||
VMutableArray_For_SplinePosition(MutableSpan<SplinePtr> splines, Array<int> offsets)
|
||||
: VMutableArray<float3>(offsets.last()), splines_(splines), offsets_(std::move(offsets))
|
||||
{
|
||||
}
|
||||
|
||||
float3 get_impl(const int64_t index) const final
|
||||
{
|
||||
const PointIndices indices = lookup_point_indices(offsets_, index);
|
||||
return splines_[indices.spline_index]->positions()[indices.point_index];
|
||||
}
|
||||
|
||||
void set_impl(const int64_t index, float3 value) final
|
||||
{
|
||||
const PointIndices indices = lookup_point_indices(offsets_, index);
|
||||
Spline &spline = *splines_[indices.spline_index];
|
||||
if (BezierSpline *bezier_spline = dynamic_cast<BezierSpline *>(&spline)) {
|
||||
const float3 delta = value - bezier_spline->positions()[indices.point_index];
|
||||
bezier_spline->handle_positions_left()[indices.point_index] += delta;
|
||||
bezier_spline->handle_positions_right()[indices.point_index] += delta;
|
||||
bezier_spline->positions()[indices.point_index] = value;
|
||||
}
|
||||
else {
|
||||
spline.positions()[indices.point_index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void set_all_impl(Span<float3> src) final
|
||||
{
|
||||
for (const int spline_index : splines_.index_range()) {
|
||||
Spline &spline = *splines_[spline_index];
|
||||
const int offset = offsets_[spline_index];
|
||||
const int next_offset = offsets_[spline_index + 1];
|
||||
if (BezierSpline *bezier_spline = dynamic_cast<BezierSpline *>(&spline)) {
|
||||
MutableSpan<float3> positions = bezier_spline->positions();
|
||||
MutableSpan<float3> handle_positions_left = bezier_spline->handle_positions_left();
|
||||
MutableSpan<float3> handle_positions_right = bezier_spline->handle_positions_right();
|
||||
for (const int i : IndexRange(next_offset - offset)) {
|
||||
const float3 delta = src[offset + i] - positions[i];
|
||||
handle_positions_left[i] += delta;
|
||||
handle_positions_right[i] += delta;
|
||||
positions[i] = src[offset + i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
spline.positions().copy_from(src.slice(offset, next_offset - offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Utility so we can pass positions to the materialize functions above. */
|
||||
Array<Span<float3>> get_position_spans() const
|
||||
{
|
||||
Array<Span<float3>> spans(splines_.size());
|
||||
for (const int i : spans.index_range()) {
|
||||
spans[i] = splines_[i]->positions();
|
||||
}
|
||||
return spans;
|
||||
}
|
||||
|
||||
void materialize_impl(const IndexMask mask, MutableSpan<float3> r_span) const final
|
||||
{
|
||||
Array<Span<float3>> spans = this->get_position_spans();
|
||||
point_attribute_materialize(spans.as_span(), offsets_, mask, r_span);
|
||||
}
|
||||
|
||||
void materialize_to_uninitialized_impl(const IndexMask mask,
|
||||
MutableSpan<float3> r_span) const final
|
||||
{
|
||||
Array<Span<float3>> spans = this->get_position_spans();
|
||||
point_attribute_materialize_to_uninitialized(spans.as_span(), offsets_, mask, r_span);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Provider for any builtin control point attribute that doesn't need
|
||||
* special handling like access to other arrays in the spline.
|
||||
*/
|
||||
template<typename T> class BuiltinPointAttributeProvider : public BuiltinAttributeProvider {
|
||||
protected:
|
||||
using GetSpan = Span<T> (*)(const Spline &spline);
|
||||
using GetMutableSpan = MutableSpan<T> (*)(Spline &spline);
|
||||
using UpdateOnWrite = void (*)(Spline &spline);
|
||||
const GetSpan get_span_;
|
||||
const GetMutableSpan get_mutable_span_;
|
||||
const UpdateOnWrite update_on_write_;
|
||||
|
||||
public:
|
||||
BuiltinPointAttributeProvider(std::string attribute_name,
|
||||
const WritableEnum writable,
|
||||
const GetSpan get_span,
|
||||
const GetMutableSpan get_mutable_span,
|
||||
const UpdateOnWrite update_on_write)
|
||||
: BuiltinAttributeProvider(std::move(attribute_name),
|
||||
ATTR_DOMAIN_POINT,
|
||||
bke::cpp_type_to_custom_data_type(CPPType::get<T>()),
|
||||
BuiltinAttributeProvider::NonCreatable,
|
||||
writable,
|
||||
BuiltinAttributeProvider::NonDeletable),
|
||||
get_span_(get_span),
|
||||
get_mutable_span_(get_mutable_span),
|
||||
update_on_write_(update_on_write)
|
||||
{
|
||||
}
|
||||
|
||||
GVArrayPtr try_get_for_read(const GeometryComponent &component) const override
|
||||
{
|
||||
const CurveEval *curve = get_curve_from_component_for_read(component);
|
||||
if (curve == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Span<SplinePtr> splines = curve->splines();
|
||||
if (splines.size() == 1) {
|
||||
return std::make_unique<fn::GVArray_For_GSpan>(get_span_(*splines.first()));
|
||||
}
|
||||
|
||||
Array<int> offsets = curve->control_point_offsets();
|
||||
Array<Span<T>> spans(splines.size());
|
||||
for (const int i : splines.index_range()) {
|
||||
spans[i] = get_span_(*splines[i]);
|
||||
}
|
||||
|
||||
return std::make_unique<fn::GVArray_For_EmbeddedVArray<T, VArray_For_SplinePoints<T>>>(
|
||||
offsets.last(), std::move(spans), std::move(offsets));
|
||||
}
|
||||
|
||||
GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const override
|
||||
{
|
||||
CurveEval *curve = get_curve_from_component_for_write(component);
|
||||
if (curve == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
MutableSpan<SplinePtr> splines = curve->splines();
|
||||
if (splines.size() == 1) {
|
||||
return std::make_unique<fn::GVMutableArray_For_GMutableSpan>(
|
||||
get_mutable_span_(*splines.first()));
|
||||
}
|
||||
|
||||
Array<int> offsets = curve->control_point_offsets();
|
||||
Array<MutableSpan<T>> spans(splines.size());
|
||||
for (const int i : splines.index_range()) {
|
||||
spans[i] = get_mutable_span_(*splines[i]);
|
||||
if (update_on_write_) {
|
||||
update_on_write_(*splines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_unique<
|
||||
fn::GVMutableArray_For_EmbeddedVMutableArray<T, VMutableArray_For_SplinePoints<T>>>(
|
||||
offsets.last(), std::move(spans), std::move(offsets));
|
||||
}
|
||||
|
||||
bool try_delete(GeometryComponent &UNUSED(component)) const final
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool try_create(GeometryComponent &UNUSED(component),
|
||||
const AttributeInit &UNUSED(initializer)) const final
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool exists(const GeometryComponent &component) const final
|
||||
{
|
||||
return component.attribute_domain_size(ATTR_DOMAIN_POINT) != 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Special attribute provider for the position attribute. Keeping this separate means we don't
|
||||
* need to make #BuiltinPointAttributeProvider overly generic, and the special handling for the
|
||||
* positions is more clear.
|
||||
*/
|
||||
class PositionAttributeProvider final : public BuiltinPointAttributeProvider<float3> {
|
||||
public:
|
||||
PositionAttributeProvider()
|
||||
: BuiltinPointAttributeProvider(
|
||||
"position",
|
||||
BuiltinAttributeProvider::Writable,
|
||||
[](const Spline &spline) { return spline.positions(); },
|
||||
[](Spline &spline) { return spline.positions(); },
|
||||
[](Spline &spline) { spline.mark_cache_invalid(); })
|
||||
{
|
||||
}
|
||||
|
||||
GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const final
|
||||
{
|
||||
CurveEval *curve = get_curve_from_component_for_write(component);
|
||||
if (curve == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool curve_has_bezier_spline = false;
|
||||
for (SplinePtr &spline : curve->splines()) {
|
||||
if (spline->type() == Spline::Type::Bezier) {
|
||||
curve_has_bezier_spline = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Use the regular position virtual array when there aren't any Bezier splines
|
||||
* to avoid the overhead of thecking the spline type for every point. */
|
||||
if (!curve_has_bezier_spline) {
|
||||
return BuiltinPointAttributeProvider<float3>::try_get_for_write(component);
|
||||
}
|
||||
|
||||
/* Changing the positions requires recalculation of cached evaluated data in many cases.
|
||||
* This could set more specific flags in the future to avoid unnecessary recomputation. */
|
||||
for (SplinePtr &spline : curve->splines()) {
|
||||
spline->mark_cache_invalid();
|
||||
}
|
||||
|
||||
Array<int> offsets = curve->control_point_offsets();
|
||||
return std::make_unique<
|
||||
fn::GVMutableArray_For_EmbeddedVMutableArray<float3, VMutableArray_For_SplinePosition>>(
|
||||
offsets.last(), curve->splines(), std::move(offsets));
|
||||
}
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Attribute Provider Declaration
|
||||
* \{ */
|
||||
|
||||
/**
|
||||
* In this function all the attribute providers for a curve component are created. Most data
|
||||
* in this function is statically allocated, because it does not change over time.
|
||||
* In this function all the attribute providers for a curve component are created.
|
||||
* Most data in this function is statically allocated, because it does not change over time.
|
||||
*/
|
||||
static ComponentAttributeProviders create_attribute_providers_for_curve()
|
||||
{
|
||||
|
@ -307,7 +704,23 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
|
|||
make_cyclic_read_attribute,
|
||||
make_cyclic_write_attribute);
|
||||
|
||||
return ComponentAttributeProviders({&resolution, &cyclic}, {});
|
||||
static PositionAttributeProvider position;
|
||||
|
||||
static BuiltinPointAttributeProvider<float> radius(
|
||||
"radius",
|
||||
BuiltinAttributeProvider::Writable,
|
||||
[](const Spline &spline) { return spline.radii(); },
|
||||
[](Spline &spline) { return spline.radii(); },
|
||||
nullptr);
|
||||
|
||||
static BuiltinPointAttributeProvider<float> tilt(
|
||||
"tilt",
|
||||
BuiltinAttributeProvider::Writable,
|
||||
[](const Spline &spline) { return spline.tilts(); },
|
||||
[](Spline &spline) { return spline.tilts(); },
|
||||
[](Spline &spline) { spline.mark_cache_invalid(); });
|
||||
|
||||
return ComponentAttributeProviders({&position, &radius, &tilt, &resolution, &cyclic}, {});
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
|
|
@ -528,11 +528,11 @@ static void join_curve_splines(Span<GeometryInstanceGroup> set_groups, CurveComp
|
|||
}
|
||||
|
||||
const CurveEval &source_curve = *set.get_curve_for_read();
|
||||
for (const SplinePtr &source_spline : source_curve.splines) {
|
||||
for (const SplinePtr &source_spline : source_curve.splines()) {
|
||||
for (const float4x4 &transform : set_group.transforms) {
|
||||
SplinePtr new_spline = source_spline->copy();
|
||||
new_spline->transform(transform);
|
||||
new_curve->splines.append(std::move(new_spline));
|
||||
new_curve->add_spline(std::move(new_spline));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -653,9 +653,13 @@ bGPDframe *BKE_gpencil_frame_addcopy(bGPDlayer *gpl, int cframe)
|
|||
* \param gpd: Grease pencil data-block
|
||||
* \param name: Name of the layer
|
||||
* \param setactive: Set as active
|
||||
* \param add_to_header: Used to force the layer added at header
|
||||
* \return Pointer to new layer
|
||||
*/
|
||||
bGPDlayer *BKE_gpencil_layer_addnew(bGPdata *gpd, const char *name, bool setactive)
|
||||
bGPDlayer *BKE_gpencil_layer_addnew(bGPdata *gpd,
|
||||
const char *name,
|
||||
const bool setactive,
|
||||
const bool add_to_header)
|
||||
{
|
||||
bGPDlayer *gpl = NULL;
|
||||
bGPDlayer *gpl_active = NULL;
|
||||
|
@ -671,14 +675,18 @@ bGPDlayer *BKE_gpencil_layer_addnew(bGPdata *gpd, const char *name, bool setacti
|
|||
gpl_active = BKE_gpencil_layer_active_get(gpd);
|
||||
|
||||
/* Add to data-block. */
|
||||
if (gpl_active == NULL) {
|
||||
BLI_addtail(&gpd->layers, gpl);
|
||||
if (add_to_header) {
|
||||
BLI_addhead(&gpd->layers, gpl);
|
||||
}
|
||||
else {
|
||||
/* if active layer, add after that layer */
|
||||
BLI_insertlinkafter(&gpd->layers, gpl_active, gpl);
|
||||
if (gpl_active == NULL) {
|
||||
BLI_addtail(&gpd->layers, gpl);
|
||||
}
|
||||
else {
|
||||
/* if active layer, add after that layer */
|
||||
BLI_insertlinkafter(&gpd->layers, gpl_active, gpl);
|
||||
}
|
||||
}
|
||||
|
||||
/* annotation vs GP Object behavior is slightly different */
|
||||
if (gpd->flag & GP_DATA_ANNOTATIONS) {
|
||||
/* set default color of new strokes for this layer */
|
||||
|
|
|
@ -515,7 +515,7 @@ void BKE_gpencil_convert_curve(Main *bmain,
|
|||
if (collection != NULL) {
|
||||
gpl = BKE_gpencil_layer_named_get(gpd, collection->id.name + 2);
|
||||
if (gpl == NULL) {
|
||||
gpl = BKE_gpencil_layer_addnew(gpd, collection->id.name + 2, true);
|
||||
gpl = BKE_gpencil_layer_addnew(gpd, collection->id.name + 2, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -523,7 +523,7 @@ void BKE_gpencil_convert_curve(Main *bmain,
|
|||
if (gpl == NULL) {
|
||||
gpl = BKE_gpencil_layer_active_get(gpd);
|
||||
if (gpl == NULL) {
|
||||
gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true);
|
||||
gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2439,7 +2439,7 @@ bool BKE_gpencil_convert_mesh(Main *bmain,
|
|||
/* Create Layer and Frame. */
|
||||
bGPDlayer *gpl_fill = BKE_gpencil_layer_named_get(gpd, element_name);
|
||||
if (gpl_fill == NULL) {
|
||||
gpl_fill = BKE_gpencil_layer_addnew(gpd, element_name, true);
|
||||
gpl_fill = BKE_gpencil_layer_addnew(gpd, element_name, true, false);
|
||||
}
|
||||
bGPDframe *gpf_fill = BKE_gpencil_layer_frame_get(
|
||||
gpl_fill, CFRA + frame_offset, GP_GETFRAME_ADD_NEW);
|
||||
|
@ -2492,7 +2492,7 @@ bool BKE_gpencil_convert_mesh(Main *bmain,
|
|||
/* Create Layer and Frame. */
|
||||
bGPDlayer *gpl_stroke = BKE_gpencil_layer_named_get(gpd, element_name);
|
||||
if (gpl_stroke == NULL) {
|
||||
gpl_stroke = BKE_gpencil_layer_addnew(gpd, element_name, true);
|
||||
gpl_stroke = BKE_gpencil_layer_addnew(gpd, element_name, true, false);
|
||||
}
|
||||
bGPDframe *gpf_stroke = BKE_gpencil_layer_frame_get(
|
||||
gpl_stroke, CFRA + frame_offset, GP_GETFRAME_ADD_NEW);
|
||||
|
|
|
@ -735,6 +735,37 @@ int BKE_image_get_tile_from_pos(struct Image *ima,
|
|||
return tile_number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tile_number for the closest UDIM tile.
|
||||
*/
|
||||
int BKE_image_find_nearest_tile(const Image *image, const float co[2])
|
||||
{
|
||||
const float co_floor[2] = {floorf(co[0]), floorf(co[1])};
|
||||
/* Distance to the closest UDIM tile. */
|
||||
float dist_best_sq = FLT_MAX;
|
||||
int tile_number_best = -1;
|
||||
|
||||
LISTBASE_FOREACH (const ImageTile *, tile, &image->tiles) {
|
||||
const int tile_index = tile->tile_number - 1001;
|
||||
/* Coordinates of the current tile. */
|
||||
const float tile_index_co[2] = {tile_index % 10, tile_index / 10};
|
||||
|
||||
if (equals_v2v2(co_floor, tile_index_co)) {
|
||||
return tile->tile_number;
|
||||
}
|
||||
|
||||
/* Distance between co[2] and UDIM tile. */
|
||||
const float dist_sq = len_squared_v2v2(tile_index_co, co);
|
||||
|
||||
if (dist_sq < dist_best_sq) {
|
||||
dist_best_sq = dist_sq;
|
||||
tile_number_best = tile->tile_number;
|
||||
}
|
||||
}
|
||||
|
||||
return tile_number_best;
|
||||
}
|
||||
|
||||
static void image_init_color_management(Image *ima)
|
||||
{
|
||||
ImBuf *ibuf;
|
||||
|
|
|
@ -1669,7 +1669,7 @@ bool BKE_lib_override_library_property_operation_operands_validate(
|
|||
return true;
|
||||
}
|
||||
|
||||
/** Check against potential \a bmain. */
|
||||
/** Check against potential \a bmain. */
|
||||
void BKE_lib_override_library_validate(Main *UNUSED(bmain), ID *id, ReportList *reports)
|
||||
{
|
||||
if (id->override_library == NULL) {
|
||||
|
@ -1703,7 +1703,7 @@ void BKE_lib_override_library_validate(Main *UNUSED(bmain), ID *id, ReportList *
|
|||
}
|
||||
}
|
||||
|
||||
/** Check against potential \a bmain. */
|
||||
/** Check against potential \a bmain. */
|
||||
void BKE_lib_override_library_main_validate(Main *bmain, ReportList *reports)
|
||||
{
|
||||
ID *id;
|
||||
|
|
|
@ -162,7 +162,7 @@ static void make_box_from_metaelem(Box *r, const MetaElem *ml)
|
|||
}
|
||||
|
||||
/**
|
||||
* Partitions part of mainb array [start, end) along axis s. Returns i,
|
||||
* Partitions part of #process.mainb array [start, end) along axis s. Returns i,
|
||||
* where centroids of elements in the [start, i) segment lie "on the right side" of div,
|
||||
* and elements in the [i, end) segment lie "on the left"
|
||||
*/
|
||||
|
@ -1170,8 +1170,9 @@ static void polygonize(PROCESS *process)
|
|||
|
||||
/**
|
||||
* Iterates over ALL objects in the scene and all of its sets, including
|
||||
* making all duplis(not only metas). Copies metas to mainb array.
|
||||
* Computes bounding boxes for building BVH. */
|
||||
* making all duplis (not only meta-elements). Copies meta-elements to #process.mainb array.
|
||||
* Computes bounding boxes for building BVH.
|
||||
*/
|
||||
static void init_meta(Depsgraph *depsgraph, PROCESS *process, Scene *scene, Object *ob)
|
||||
{
|
||||
Scene *sce_iter = scene;
|
||||
|
|
|
@ -303,6 +303,16 @@ static void library_foreach_node_socket(LibraryForeachIDData *data, bNodeSocket
|
|||
BKE_LIB_FOREACHID_PROCESS(data, default_value->value, IDWALK_CB_USER);
|
||||
break;
|
||||
}
|
||||
case SOCK_TEXTURE: {
|
||||
bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value;
|
||||
BKE_LIB_FOREACHID_PROCESS(data, default_value->value, IDWALK_CB_USER);
|
||||
break;
|
||||
}
|
||||
case SOCK_MATERIAL: {
|
||||
bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value;
|
||||
BKE_LIB_FOREACHID_PROCESS(data, default_value->value, IDWALK_CB_USER);
|
||||
break;
|
||||
}
|
||||
case SOCK_FLOAT:
|
||||
case SOCK_VECTOR:
|
||||
case SOCK_RGBA:
|
||||
|
@ -434,6 +444,12 @@ static void write_node_socket_default_value(BlendWriter *writer, bNodeSocket *so
|
|||
case SOCK_COLLECTION:
|
||||
BLO_write_struct(writer, bNodeSocketValueCollection, sock->default_value);
|
||||
break;
|
||||
case SOCK_TEXTURE:
|
||||
BLO_write_struct(writer, bNodeSocketValueTexture, sock->default_value);
|
||||
break;
|
||||
case SOCK_MATERIAL:
|
||||
BLO_write_struct(writer, bNodeSocketValueMaterial, sock->default_value);
|
||||
break;
|
||||
case __SOCK_MESH:
|
||||
case SOCK_CUSTOM:
|
||||
case SOCK_SHADER:
|
||||
|
@ -820,6 +836,16 @@ static void lib_link_node_socket(BlendLibReader *reader, Library *lib, bNodeSock
|
|||
BLO_read_id_address(reader, lib, &default_value->value);
|
||||
break;
|
||||
}
|
||||
case SOCK_TEXTURE: {
|
||||
bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value;
|
||||
BLO_read_id_address(reader, lib, &default_value->value);
|
||||
break;
|
||||
}
|
||||
case SOCK_MATERIAL: {
|
||||
bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value;
|
||||
BLO_read_id_address(reader, lib, &default_value->value);
|
||||
break;
|
||||
}
|
||||
case SOCK_FLOAT:
|
||||
case SOCK_VECTOR:
|
||||
case SOCK_RGBA:
|
||||
|
@ -905,6 +931,16 @@ static void expand_node_socket(BlendExpander *expander, bNodeSocket *sock)
|
|||
BLO_expand(expander, default_value->value);
|
||||
break;
|
||||
}
|
||||
case SOCK_TEXTURE: {
|
||||
bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value;
|
||||
BLO_expand(expander, default_value->value);
|
||||
break;
|
||||
}
|
||||
case SOCK_MATERIAL: {
|
||||
bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value;
|
||||
BLO_expand(expander, default_value->value);
|
||||
break;
|
||||
}
|
||||
case SOCK_FLOAT:
|
||||
case SOCK_VECTOR:
|
||||
case SOCK_RGBA:
|
||||
|
@ -1472,6 +1508,16 @@ static void socket_id_user_increment(bNodeSocket *sock)
|
|||
id_us_plus((ID *)default_value->value);
|
||||
break;
|
||||
}
|
||||
case SOCK_TEXTURE: {
|
||||
bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value;
|
||||
id_us_plus((ID *)default_value->value);
|
||||
break;
|
||||
}
|
||||
case SOCK_MATERIAL: {
|
||||
bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value;
|
||||
id_us_plus((ID *)default_value->value);
|
||||
break;
|
||||
}
|
||||
case SOCK_FLOAT:
|
||||
case SOCK_VECTOR:
|
||||
case SOCK_RGBA:
|
||||
|
@ -1511,6 +1557,20 @@ static void socket_id_user_decrement(bNodeSocket *sock)
|
|||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_TEXTURE: {
|
||||
bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value;
|
||||
if (default_value->value != nullptr) {
|
||||
id_us_min(&default_value->value->id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_MATERIAL: {
|
||||
bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value;
|
||||
if (default_value->value != nullptr) {
|
||||
id_us_min(&default_value->value->id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_FLOAT:
|
||||
case SOCK_VECTOR:
|
||||
case SOCK_RGBA:
|
||||
|
@ -1654,6 +1714,10 @@ const char *nodeStaticSocketType(int type, int subtype)
|
|||
return "NodeSocketGeometry";
|
||||
case SOCK_COLLECTION:
|
||||
return "NodeSocketCollection";
|
||||
case SOCK_TEXTURE:
|
||||
return "NodeSocketTexture";
|
||||
case SOCK_MATERIAL:
|
||||
return "NodeSocketMaterial";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -1725,6 +1789,10 @@ const char *nodeStaticSocketInterfaceType(int type, int subtype)
|
|||
return "NodeSocketInterfaceGeometry";
|
||||
case SOCK_COLLECTION:
|
||||
return "NodeSocketInterfaceCollection";
|
||||
case SOCK_TEXTURE:
|
||||
return "NodeSocketInterfaceTexture";
|
||||
case SOCK_MATERIAL:
|
||||
return "NodeSocketInterfaceMaterial";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -2190,6 +2258,17 @@ bNodeTree *ntreeCopyTree_ex_new_pointers(const bNodeTree *ntree,
|
|||
return new_ntree;
|
||||
}
|
||||
|
||||
static int node_count_links(const bNodeTree *ntree, const bNodeSocket *socket)
|
||||
{
|
||||
int count = 0;
|
||||
LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) {
|
||||
if (ELEM(socket, link->fromsock, link->tosock)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* also used via rna api, so we check for proper input output direction */
|
||||
bNodeLink *nodeAddLink(
|
||||
bNodeTree *ntree, bNode *fromnode, bNodeSocket *fromsock, bNode *tonode, bNodeSocket *tosock)
|
||||
|
@ -2226,6 +2305,10 @@ bNodeLink *nodeAddLink(
|
|||
ntree->update |= NTREE_UPDATE_LINKS;
|
||||
}
|
||||
|
||||
if (link->tosock->flag & SOCK_MULTI_INPUT) {
|
||||
link->multi_input_socket_index = node_count_links(ntree, link->tosock) - 1;
|
||||
}
|
||||
|
||||
return link;
|
||||
}
|
||||
|
||||
|
@ -4244,7 +4327,7 @@ void ntreeUpdateAllUsers(Main *main, ID *id)
|
|||
|
||||
if (GS(id->name) == ID_NT) {
|
||||
bNodeTree *ngroup = (bNodeTree *)id;
|
||||
if (ngroup->type == NTREE_GEOMETRY) {
|
||||
if (ngroup->type == NTREE_GEOMETRY && (ngroup->update & NTREE_UPDATE_GROUP)) {
|
||||
LISTBASE_FOREACH (Object *, object, &main->objects) {
|
||||
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
|
||||
if (md->type == eModifierType_Nodes) {
|
||||
|
|
|
@ -152,6 +152,7 @@ void BKE_nodetree_error_message_add(bNodeTree &ntree,
|
|||
node_error_message_log(ntree, node, message, type);
|
||||
|
||||
NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ntree, context, node);
|
||||
std::lock_guard lock{node_ui_storage.mutex};
|
||||
node_ui_storage.warnings.append({type, std::move(message)});
|
||||
}
|
||||
|
||||
|
@ -163,6 +164,7 @@ void BKE_nodetree_attribute_hint_add(bNodeTree &ntree,
|
|||
const CustomDataType data_type)
|
||||
{
|
||||
NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ntree, context, node);
|
||||
std::lock_guard lock{node_ui_storage.mutex};
|
||||
node_ui_storage.attribute_hints.add_as(
|
||||
AvailableAttributeInfo{attribute_name, domain, data_type});
|
||||
}
|
||||
|
|
|
@ -119,10 +119,12 @@ MutableSpan<BezierSpline::HandleType> BezierSpline::handle_types_left()
|
|||
}
|
||||
Span<float3> BezierSpline::handle_positions_left() const
|
||||
{
|
||||
this->ensure_auto_handles();
|
||||
return handle_positions_left_;
|
||||
}
|
||||
MutableSpan<float3> BezierSpline::handle_positions_left()
|
||||
{
|
||||
this->ensure_auto_handles();
|
||||
return handle_positions_left_;
|
||||
}
|
||||
Span<BezierSpline::HandleType> BezierSpline::handle_types_right() const
|
||||
|
@ -135,13 +137,94 @@ MutableSpan<BezierSpline::HandleType> BezierSpline::handle_types_right()
|
|||
}
|
||||
Span<float3> BezierSpline::handle_positions_right() const
|
||||
{
|
||||
this->ensure_auto_handles();
|
||||
return handle_positions_right_;
|
||||
}
|
||||
MutableSpan<float3> BezierSpline::handle_positions_right()
|
||||
{
|
||||
this->ensure_auto_handles();
|
||||
return handle_positions_right_;
|
||||
}
|
||||
|
||||
static float3 previous_position(Span<float3> positions, const bool cyclic, const int i)
|
||||
{
|
||||
if (i == 0) {
|
||||
if (cyclic) {
|
||||
return positions[positions.size() - 1];
|
||||
}
|
||||
return 2.0f * positions[i] - positions[i + 1];
|
||||
}
|
||||
return positions[i - 1];
|
||||
}
|
||||
|
||||
static float3 next_position(Span<float3> positions, const bool cyclic, const int i)
|
||||
{
|
||||
if (i == positions.size() - 1) {
|
||||
if (cyclic) {
|
||||
return positions[0];
|
||||
}
|
||||
return 2.0f * positions[i] - positions[i - 1];
|
||||
}
|
||||
return positions[i + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate all #Auto and #Vector handles with positions automatically
|
||||
* derived from the neighboring control points.
|
||||
*/
|
||||
void BezierSpline::ensure_auto_handles() const
|
||||
{
|
||||
if (!auto_handles_dirty_) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard lock{auto_handle_mutex_};
|
||||
if (!auto_handles_dirty_) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const int i : IndexRange(this->size())) {
|
||||
if (ELEM(HandleType::Auto, handle_types_left_[i], handle_types_right_[i])) {
|
||||
const float3 prev_diff = positions_[i] - previous_position(positions_, is_cyclic_, i);
|
||||
const float3 next_diff = next_position(positions_, is_cyclic_, i) - positions_[i];
|
||||
float prev_len = prev_diff.length();
|
||||
float next_len = next_diff.length();
|
||||
if (prev_len == 0.0f) {
|
||||
prev_len = 1.0f;
|
||||
}
|
||||
if (next_len == 0.0f) {
|
||||
next_len = 1.0f;
|
||||
}
|
||||
const float3 dir = next_diff / next_len + prev_diff / prev_len;
|
||||
|
||||
/* This magic number is unfortunate, but comes from elsewhere in Blender. */
|
||||
const float len = dir.length() * 2.5614f;
|
||||
if (len != 0.0f) {
|
||||
if (handle_types_left_[i] == HandleType::Auto) {
|
||||
const float prev_len_clamped = std::min(prev_len, next_len * 5.0f);
|
||||
handle_positions_left_[i] = positions_[i] + dir * -(prev_len_clamped / len);
|
||||
}
|
||||
if (handle_types_right_[i] == HandleType::Auto) {
|
||||
const float next_len_clamped = std::min(next_len, prev_len * 5.0f);
|
||||
handle_positions_right_[i] = positions_[i] + dir * (next_len_clamped / len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (handle_types_left_[i] == HandleType::Vector) {
|
||||
const float3 prev = previous_position(positions_, is_cyclic_, i);
|
||||
handle_positions_left_[i] = float3::interpolate(positions_[i], prev, 1.0f / 3.0f);
|
||||
}
|
||||
|
||||
if (handle_types_right_[i] == HandleType::Vector) {
|
||||
const float3 next = next_position(positions_, is_cyclic_, i);
|
||||
handle_positions_right_[i] = float3::interpolate(positions_[i], next, 1.0f / 3.0f);
|
||||
}
|
||||
}
|
||||
|
||||
auto_handles_dirty_ = false;
|
||||
}
|
||||
|
||||
void BezierSpline::translate(const blender::float3 &translation)
|
||||
{
|
||||
for (float3 &position : this->positions()) {
|
||||
|
@ -179,9 +262,13 @@ bool BezierSpline::point_is_sharp(const int index) const
|
|||
bool BezierSpline::segment_is_vector(const int index) const
|
||||
{
|
||||
if (index == this->size() - 1) {
|
||||
BLI_assert(is_cyclic_);
|
||||
return handle_types_right_.last() == HandleType::Vector &&
|
||||
handle_types_left_.first() == HandleType::Vector;
|
||||
if (is_cyclic_) {
|
||||
return handle_types_right_.last() == HandleType::Vector &&
|
||||
handle_types_left_.first() == HandleType::Vector;
|
||||
}
|
||||
/* There is actually no segment in this case, but it's nice to avoid
|
||||
* having a special case for the last segment in calling code. */
|
||||
return true;
|
||||
}
|
||||
return handle_types_right_[index] == HandleType::Vector &&
|
||||
handle_types_left_[index + 1] == HandleType::Vector;
|
||||
|
@ -195,19 +282,13 @@ void BezierSpline::mark_cache_invalid()
|
|||
tangent_cache_dirty_ = true;
|
||||
normal_cache_dirty_ = true;
|
||||
length_cache_dirty_ = true;
|
||||
auto_handles_dirty_ = true;
|
||||
}
|
||||
|
||||
int BezierSpline::evaluated_points_size() const
|
||||
{
|
||||
const int points_len = this->size();
|
||||
BLI_assert(points_len > 0);
|
||||
|
||||
const int last_offset = this->control_point_offsets().last();
|
||||
if (is_cyclic_ && points_len > 1) {
|
||||
return last_offset + (this->segment_is_vector(points_len - 1) ? 1 : resolution_);
|
||||
}
|
||||
|
||||
return last_offset + 1;
|
||||
BLI_assert(this->size() > 0);
|
||||
return this->control_point_offsets().last();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -278,8 +359,12 @@ void BezierSpline::evaluate_bezier_segment(const int index,
|
|||
|
||||
/**
|
||||
* Returns access to a cache of offsets into the evaluated point array for each control point.
|
||||
* This is important because while most control point edges generate the number of edges specified
|
||||
* by the resolution, vector segments only generate one edge.
|
||||
* While most control point edges generate the number of edges specified by the resolution, vector
|
||||
* segments only generate one edge.
|
||||
*
|
||||
* \note The length of the result is one greater than the number of points, so that the last item
|
||||
* is the total number of evaluated points. This is useful to avoid recalculating the size of the
|
||||
* last segment everywhere.
|
||||
*/
|
||||
Span<int> BezierSpline::control_point_offsets() const
|
||||
{
|
||||
|
@ -293,12 +378,12 @@ Span<int> BezierSpline::control_point_offsets() const
|
|||
}
|
||||
|
||||
const int points_len = this->size();
|
||||
offset_cache_.resize(points_len);
|
||||
offset_cache_.resize(points_len + 1);
|
||||
|
||||
MutableSpan<int> offsets = offset_cache_;
|
||||
|
||||
int offset = 0;
|
||||
for (const int i : IndexRange(points_len - 1)) {
|
||||
for (const int i : IndexRange(points_len)) {
|
||||
offsets[i] = offset;
|
||||
offset += this->segment_is_vector(i) ? 1 : resolution_;
|
||||
}
|
||||
|
@ -320,7 +405,7 @@ static void calculate_mappings_linear_resolution(Span<int> offsets,
|
|||
}
|
||||
|
||||
const int grain_size = std::max(2048 / resolution, 1);
|
||||
blender::parallel_for(IndexRange(1, size - 2), grain_size, [&](IndexRange range) {
|
||||
parallel_for(IndexRange(1, size - 2), grain_size, [&](IndexRange range) {
|
||||
for (const int i_control_point : range) {
|
||||
const int segment_len = offsets[i_control_point + 1] - offsets[i_control_point];
|
||||
const float segment_len_inv = 1.0f / segment_len;
|
||||
|
@ -331,7 +416,7 @@ static void calculate_mappings_linear_resolution(Span<int> offsets,
|
|||
});
|
||||
|
||||
if (is_cyclic) {
|
||||
const int last_segment_len = offsets[size - 1] - offsets[size - 2];
|
||||
const int last_segment_len = offsets[size] - offsets[size - 1];
|
||||
const float last_segment_len_inv = 1.0f / last_segment_len;
|
||||
for (const int i : IndexRange(last_segment_len)) {
|
||||
r_mappings[offsets[size - 1] + i] = size - 1 + i * last_segment_len_inv;
|
||||
|
@ -389,25 +474,26 @@ Span<float3> BezierSpline::evaluated_positions() const
|
|||
return evaluated_position_cache_;
|
||||
}
|
||||
|
||||
this->ensure_auto_handles();
|
||||
|
||||
const int size = this->size();
|
||||
const int eval_size = this->evaluated_points_size();
|
||||
evaluated_position_cache_.resize(eval_size);
|
||||
|
||||
MutableSpan<float3> positions = evaluated_position_cache_;
|
||||
|
||||
Span<int> offsets = this->control_point_offsets();
|
||||
BLI_assert(offsets.last() <= eval_size);
|
||||
|
||||
const int grain_size = std::max(512 / resolution_, 1);
|
||||
blender::parallel_for(IndexRange(this->size() - 1), grain_size, [&](IndexRange range) {
|
||||
parallel_for(IndexRange(size - 1), grain_size, [&](IndexRange range) {
|
||||
for (const int i : range) {
|
||||
this->evaluate_bezier_segment(
|
||||
i, i + 1, positions.slice(offsets[i], offsets[i + 1] - offsets[i]));
|
||||
}
|
||||
});
|
||||
|
||||
const int i_last = this->size() - 1;
|
||||
if (is_cyclic_) {
|
||||
this->evaluate_bezier_segment(i_last, 0, positions.slice(offsets.last(), resolution_));
|
||||
this->evaluate_bezier_segment(
|
||||
size - 1, 0, positions.slice(offsets[size - 1], offsets[size] - offsets[size - 1]));
|
||||
}
|
||||
else {
|
||||
/* Since evaluating the bezier segment doesn't add the final point,
|
||||
|
@ -422,7 +508,7 @@ Span<float3> BezierSpline::evaluated_positions() const
|
|||
/**
|
||||
* Convert the data encoded in #evaulated_mappings into its parts-- the information necessary
|
||||
* to interpolate data from control points to evaluated points between them. The next control
|
||||
* point index result will not overflow the size of the vector.
|
||||
* point index result will not overflow the size of the control point vectors.
|
||||
*/
|
||||
BezierSpline::InterpolationData BezierSpline::interpolation_data_from_index_factor(
|
||||
const float index_factor) const
|
||||
|
|
|
@ -259,7 +259,7 @@ static void calculate_basis_for_point(const float parameter,
|
|||
MutableSpan<float> basis_buffer,
|
||||
NURBSpline::BasisCache &basis_cache)
|
||||
{
|
||||
/* Clamp parameter due to floating point inaccuracy. TODO: Look into using doubles. */
|
||||
/* Clamp parameter due to floating point inaccuracy. */
|
||||
const float t = std::clamp(parameter, knots[0], knots[points_len + order - 1]);
|
||||
|
||||
int start = 0;
|
||||
|
|
|
@ -82,6 +82,10 @@ typedef struct NlaEvalChannelSnapshot {
|
|||
/** For an upper snapshot channel, marks values that should be blended. */
|
||||
NlaValidMask blend_domain;
|
||||
|
||||
/** Only used for keyframe remapping. Any values not in the \a remap_domain will not be used
|
||||
* for keyframe remapping. */
|
||||
NlaValidMask remap_domain;
|
||||
|
||||
int length; /* Number of values in the property. */
|
||||
bool is_base; /* Base snapshot of the channel. */
|
||||
|
||||
|
@ -196,6 +200,13 @@ void nlasnapshot_blend(NlaEvalData *eval_data,
|
|||
const float upper_influence,
|
||||
NlaEvalSnapshot *r_blended_snapshot);
|
||||
|
||||
void nlasnapshot_blend_get_inverted_upper_snapshot(NlaEvalData *eval_data,
|
||||
NlaEvalSnapshot *lower_snapshot,
|
||||
NlaEvalSnapshot *blended_snapshot,
|
||||
const short upper_blendmode,
|
||||
const float upper_influence,
|
||||
NlaEvalSnapshot *r_upper_snapshot);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef WITH_TBB
|
||||
# include <tbb/enumerable_thread_specific.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_utility_mixins.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
namespace enumerable_thread_specific_utils {
|
||||
inline std::atomic<int> next_id = 0;
|
||||
inline thread_local int thread_id = next_id.fetch_add(1, std::memory_order_relaxed);
|
||||
} // namespace enumerable_thread_specific_utils
|
||||
|
||||
/**
|
||||
* This is mainly a wrapper for `tbb::enumerable_thread_specific`. The wrapper is needed because we
|
||||
* want to be able to build without tbb.
|
||||
*
|
||||
* More features of the tbb version can be wrapped when they are used.
|
||||
*/
|
||||
template<typename T> class EnumerableThreadSpecific : NonCopyable, NonMovable {
|
||||
#ifdef WITH_TBB
|
||||
|
||||
private:
|
||||
tbb::enumerable_thread_specific<T> values_;
|
||||
|
||||
public:
|
||||
T &local()
|
||||
{
|
||||
return values_.local();
|
||||
}
|
||||
|
||||
#else /* WITH_TBB */
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
/* Maps thread ids to their corresponding values. The values are not embedded in the map, so that
|
||||
* their addresses do not change when the map grows. */
|
||||
Map<int, std::unique_ptr<T>> values_;
|
||||
|
||||
public:
|
||||
T &local()
|
||||
{
|
||||
const int thread_id = enumerable_thread_specific_utils::thread_id;
|
||||
std::lock_guard lock{mutex_};
|
||||
return *values_.lookup_or_add_cb(thread_id, []() { return std::make_unique<T>(); });
|
||||
}
|
||||
|
||||
#endif /* WITH_TBB */
|
||||
};
|
||||
|
||||
} // namespace blender
|
|
@ -85,9 +85,12 @@
|
|||
namespace blender {
|
||||
|
||||
/**
|
||||
* If there is no other specialization of #DefaultHash for a given type, try to call `hash()` on
|
||||
* the value. If there is no such method, this will result in a compiler error. Usually that means
|
||||
* that you have to implement a hash function using one of three strategies listed above.
|
||||
* If there is no other specialization of #DefaultHash for a given type, look for a hash function
|
||||
* on the type itself. Implementing a `hash()` method on a type is often significantly easier than
|
||||
* specializing #DefaultHash.
|
||||
*
|
||||
* To support heterogeneous lookup, a type can also implement a static `hash_as(const OtherType &)`
|
||||
* function.
|
||||
*
|
||||
* In the case of an enum type, the default hash is just to cast the enum value to an integer.
|
||||
*/
|
||||
|
@ -95,12 +98,25 @@ template<typename T> struct DefaultHash {
|
|||
uint64_t operator()(const T &value) const
|
||||
{
|
||||
if constexpr (std::is_enum_v<T>) {
|
||||
/* For enums use the value as hash directly. */
|
||||
return (uint64_t)value;
|
||||
}
|
||||
else {
|
||||
/* Try to call the `hash()` function on the value. */
|
||||
/* If this results in a compiler error, no hash function for the type has been found. */
|
||||
return value.hash();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename U> uint64_t operator()(const U &value) const
|
||||
{
|
||||
/* Try calling the static `T::hash_as(value)` function with the given value. The returned hash
|
||||
* should be "compatible" with `T::hash()`. Usually that means that if `value` is converted to
|
||||
* `T` its hash does not change. */
|
||||
/* If this results in a compiler error, no hash function for the heterogeneous lookup has been
|
||||
* found. */
|
||||
return T::hash_as(value);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -128,6 +128,21 @@ template<typename Allocator = GuardedAllocator> class LinearAllocator : NonCopya
|
|||
return destruct_ptr<T>(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct multiple instances of a type in an array. The constructor of is called with the
|
||||
* given arguments. The caller is responsible for calling the destructor (and not `delete`) on
|
||||
* the constructed elements.
|
||||
*/
|
||||
template<typename T, typename... Args>
|
||||
MutableSpan<T> construct_array(int64_t size, Args &&... args)
|
||||
{
|
||||
MutableSpan<T> array = this->allocate_array<T>(size);
|
||||
for (const int64_t i : IndexRange(size)) {
|
||||
new (&array[i]) T(std::forward<Args>(args)...);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the given array into a memory buffer provided by this allocator.
|
||||
*/
|
||||
|
|
|
@ -604,6 +604,37 @@ class Map {
|
|||
return this->lookup_or_add_cb_as(std::forward<ForwardKey>(key), []() { return Value(); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key that is stored in the set that compares equal to the given key. This invokes
|
||||
* undefined behavior when the key is not in the map.
|
||||
*/
|
||||
const Key &lookup_key(const Key &key) const
|
||||
{
|
||||
return this->lookup_key_as(key);
|
||||
}
|
||||
template<typename ForwardKey> const Key &lookup_key_as(const ForwardKey &key) const
|
||||
{
|
||||
const Slot &slot = this->lookup_slot(key, hash_(key));
|
||||
return *slot.key();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pointer to the key that is stored in the map that compares equal to the given key.
|
||||
* If the key is not in the map, null is returned.
|
||||
*/
|
||||
const Key *lookup_key_ptr(const Key &key) const
|
||||
{
|
||||
return this->lookup_key_ptr_as(key);
|
||||
}
|
||||
template<typename ForwardKey> const Key *lookup_key_ptr_as(const ForwardKey &key) const
|
||||
{
|
||||
const Slot *slot = this->lookup_slot_ptr(key, hash_(key));
|
||||
if (slot == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return slot->key();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the provided callback for every key-value-pair in the map. The callback is expected
|
||||
* to take a `const Key &` as first and a `const Value &` as second parameter.
|
||||
|
|
|
@ -414,6 +414,38 @@ class VectorSet {
|
|||
return this->index_of_or_add__impl(std::forward<ForwardKey>(key), hash_(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key that is stored in the vector set that compares equal to the given key. This
|
||||
* invokes undefined behavior when the key is not in the set.
|
||||
*/
|
||||
const Key &lookup_key(const Key &key) const
|
||||
{
|
||||
return this->lookup_key_as(key);
|
||||
}
|
||||
template<typename ForwardKey> const Key &lookup_key_as(const ForwardKey &key) const
|
||||
{
|
||||
const Key *key_ptr = this->lookup_key_ptr_as(key);
|
||||
BLI_assert(key_ptr != nullptr);
|
||||
return *key_ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pointer to the key that is stored in the vector set that compares equal to the given
|
||||
* key. If the key is not in the set, null is returned.
|
||||
*/
|
||||
const Key *lookup_key_ptr(const Key &key) const
|
||||
{
|
||||
return this->lookup_key_ptr_as(key);
|
||||
}
|
||||
template<typename ForwardKey> const Key *lookup_key_ptr_as(const ForwardKey &key) const
|
||||
{
|
||||
const int64_t index = this->index_of_try__impl(key, hash_(key));
|
||||
if (index >= 0) {
|
||||
return keys_ + index;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a pointer to the beginning of the array containing all keys.
|
||||
*/
|
||||
|
|
|
@ -185,6 +185,7 @@ set(SRC
|
|||
BLI_edgehash.h
|
||||
BLI_endian_switch.h
|
||||
BLI_endian_switch_inline.h
|
||||
BLI_enumerable_thread_specific.hh
|
||||
BLI_expr_pylike_eval.h
|
||||
BLI_fileops.h
|
||||
BLI_fileops_types.h
|
||||
|
|
|
@ -136,4 +136,17 @@ TEST(linear_allocator, ManyAllocations)
|
|||
}
|
||||
}
|
||||
|
||||
TEST(linear_allocator, ConstructArray)
|
||||
{
|
||||
LinearAllocator<> allocator;
|
||||
MutableSpan<std::string> strings = allocator.construct_array<std::string>(4, "hello");
|
||||
EXPECT_EQ(strings[0], "hello");
|
||||
EXPECT_EQ(strings[1], "hello");
|
||||
EXPECT_EQ(strings[2], "hello");
|
||||
EXPECT_EQ(strings[3], "hello");
|
||||
for (std::string &string : strings) {
|
||||
string.~basic_string();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::tests
|
||||
|
|
|
@ -640,6 +640,19 @@ TEST(map, RemoveDuringIteration)
|
|||
EXPECT_EQ(map.lookup(3), 3);
|
||||
}
|
||||
|
||||
TEST(map, LookupKey)
|
||||
{
|
||||
Map<std::string, int> map;
|
||||
map.add("a", 0);
|
||||
map.add("b", 1);
|
||||
map.add("c", 2);
|
||||
EXPECT_EQ(map.lookup_key("a"), "a");
|
||||
EXPECT_EQ(map.lookup_key_as("c"), "c");
|
||||
EXPECT_EQ(map.lookup_key_ptr_as("d"), nullptr);
|
||||
EXPECT_EQ(map.lookup_key_ptr_as("b")->size(), 1);
|
||||
EXPECT_EQ(map.lookup_key_ptr("a"), map.lookup_key_ptr_as("a"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to 1 to activate the benchmark. It is disabled by default, because it prints a lot.
|
||||
*/
|
||||
|
|
|
@ -258,4 +258,17 @@ TEST(vector_set, Clear)
|
|||
EXPECT_EQ(set.size(), 0);
|
||||
}
|
||||
|
||||
TEST(vector_set, LookupKey)
|
||||
{
|
||||
VectorSet<std::string> set;
|
||||
set.add("a");
|
||||
set.add("b");
|
||||
set.add("c");
|
||||
EXPECT_EQ(set.lookup_key("a"), "a");
|
||||
EXPECT_EQ(set.lookup_key_as("c"), "c");
|
||||
EXPECT_EQ(set.lookup_key_ptr_as("d"), nullptr);
|
||||
EXPECT_EQ(set.lookup_key_ptr_as("b")->size(), 1);
|
||||
EXPECT_EQ(set.lookup_key_ptr("a"), set.lookup_key_ptr_as("a"));
|
||||
}
|
||||
|
||||
} // namespace blender::tests
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
#include "BKE_lib_id.h"
|
||||
#include "BKE_main.h"
|
||||
#include "BKE_node.h"
|
||||
|
||||
#include "BLO_readfile.h"
|
||||
#include "readfile.h"
|
||||
|
@ -57,6 +58,30 @@ void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports))
|
|||
*/
|
||||
{
|
||||
/* Keep this block, even when empty. */
|
||||
|
||||
/* Use new texture socket in Attribute Sample Texture node. */
|
||||
LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) {
|
||||
if (ntree->type != NTREE_GEOMETRY) {
|
||||
continue;
|
||||
}
|
||||
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
|
||||
if (node->type != GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE) {
|
||||
continue;
|
||||
}
|
||||
if (node->id == NULL) {
|
||||
continue;
|
||||
}
|
||||
LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) {
|
||||
if (socket->type == SOCK_TEXTURE) {
|
||||
bNodeSocketValueTexture *socket_value = (bNodeSocketValueTexture *)
|
||||
socket->default_value;
|
||||
socket_value->value = (Tex *)node->id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
node->id = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,4 +30,4 @@ double ChunkOrderHotspot::calc_distance(int x, int y)
|
|||
return result;
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
} // namespace blender::compositor
|
||||
|
|
|
@ -1557,6 +1557,12 @@ void DepsgraphNodeBuilder::build_nodetree_socket(bNodeSocket *socket)
|
|||
else if (socket->type == SOCK_COLLECTION) {
|
||||
build_id((ID *)((bNodeSocketValueCollection *)socket->default_value)->value);
|
||||
}
|
||||
else if (socket->type == SOCK_TEXTURE) {
|
||||
build_id((ID *)((bNodeSocketValueTexture *)socket->default_value)->value);
|
||||
}
|
||||
else if (socket->type == SOCK_MATERIAL) {
|
||||
build_id((ID *)((bNodeSocketValueMaterial *)socket->default_value)->value);
|
||||
}
|
||||
}
|
||||
|
||||
void DepsgraphNodeBuilder::build_nodetree(bNodeTree *ntree)
|
||||
|
|
|
@ -2401,6 +2401,18 @@ void DepsgraphRelationBuilder::build_nodetree_socket(bNodeSocket *socket)
|
|||
build_collection(nullptr, nullptr, collection);
|
||||
}
|
||||
}
|
||||
else if (socket->type == SOCK_TEXTURE) {
|
||||
Tex *texture = ((bNodeSocketValueTexture *)socket->default_value)->value;
|
||||
if (texture != nullptr) {
|
||||
build_texture(texture);
|
||||
}
|
||||
}
|
||||
else if (socket->type == SOCK_MATERIAL) {
|
||||
Material *material = ((bNodeSocketValueMaterial *)socket->default_value)->value;
|
||||
if (material != nullptr) {
|
||||
build_material(material);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DepsgraphRelationBuilder::build_nodetree(bNodeTree *ntree)
|
||||
|
|
|
@ -478,6 +478,7 @@ add_dependencies(bf_draw bf_dna)
|
|||
if(WITH_GTESTS)
|
||||
if(WITH_OPENGL_DRAW_TESTS)
|
||||
set(TEST_SRC
|
||||
tests/draw_testing.cc
|
||||
tests/shaders_test.cc
|
||||
)
|
||||
set(TEST_INC
|
||||
|
|
|
@ -158,9 +158,9 @@ void EEVEE_cryptomatte_output_init(EEVEE_ViewLayerData *UNUSED(sldata),
|
|||
const ViewLayer *view_layer = draw_ctx->view_layer;
|
||||
|
||||
const int num_cryptomatte_layers = eevee_cryptomatte_layers_count(view_layer);
|
||||
eGPUTextureFormat format = (num_cryptomatte_layers == 1) ? GPU_R32F :
|
||||
(num_cryptomatte_layers == 2) ? GPU_RG32F :
|
||||
GPU_RGBA32F;
|
||||
eGPUTextureFormat format = (num_cryptomatte_layers == 1) ?
|
||||
GPU_R32F :
|
||||
(num_cryptomatte_layers == 2) ? GPU_RG32F : GPU_RGBA32F;
|
||||
const float *viewport_size = DRW_viewport_size_get();
|
||||
const int buffer_size = viewport_size[0] * viewport_size[1];
|
||||
|
||||
|
|
|
@ -556,6 +556,11 @@ static void eevee_render_to_image(void *vedata,
|
|||
EEVEE_renderpasses_output_init(
|
||||
sldata, vedata, g_data->render_sample_count_per_timestep * time_steps_tot);
|
||||
|
||||
if (scene->world) {
|
||||
/* Update world in case of animated world material. */
|
||||
eevee_id_world_update(vedata, scene->world);
|
||||
}
|
||||
|
||||
EEVEE_temporal_sampling_create_view(vedata);
|
||||
EEVEE_render_draw(vedata, engine, render_layer, rect);
|
||||
|
||||
|
|
|
@ -51,53 +51,72 @@
|
|||
closure_##t2##_##subroutine(in_##t2##_2, eval_##t2##_2, cl_common, sub_data, out_##t2##_2); \
|
||||
closure_##t3##_##subroutine(in_##t3##_3, eval_##t3##_3, cl_common, sub_data, out_##t3##_3);
|
||||
|
||||
#ifndef DEPTH_SHADER
|
||||
/* Inputs are inout so that callers can get the final inputs used for evaluation. */
|
||||
#define CLOSURE_EVAL_FUNCTION_DECLARE(name, t0, t1, t2, t3) \
|
||||
void closure_##name##_eval(ClosureInputCommon in_common, \
|
||||
inout ClosureInput##t0 in_##t0##_0, \
|
||||
inout ClosureInput##t1 in_##t1##_1, \
|
||||
inout ClosureInput##t2 in_##t2##_2, \
|
||||
inout ClosureInput##t3 in_##t3##_3, \
|
||||
out ClosureOutput##t0 out_##t0##_0, \
|
||||
out ClosureOutput##t1 out_##t1##_1, \
|
||||
out ClosureOutput##t2 out_##t2##_2, \
|
||||
out ClosureOutput##t3 out_##t3##_3) \
|
||||
{ \
|
||||
CLOSURE_EVAL_DECLARE(t0, t1, t2, t3); \
|
||||
# define CLOSURE_EVAL_FUNCTION_DECLARE(name, t0, t1, t2, t3) \
|
||||
void closure_##name##_eval(ClosureInputCommon in_common, \
|
||||
inout ClosureInput##t0 in_##t0##_0, \
|
||||
inout ClosureInput##t1 in_##t1##_1, \
|
||||
inout ClosureInput##t2 in_##t2##_2, \
|
||||
inout ClosureInput##t3 in_##t3##_3, \
|
||||
out ClosureOutput##t0 out_##t0##_0, \
|
||||
out ClosureOutput##t1 out_##t1##_1, \
|
||||
out ClosureOutput##t2 out_##t2##_2, \
|
||||
out ClosureOutput##t3 out_##t3##_3) \
|
||||
{ \
|
||||
CLOSURE_EVAL_DECLARE(t0, t1, t2, t3); \
|
||||
\
|
||||
/* Starts at 1 because 0 is world cubemap. */ \
|
||||
for (int i = 1; cl_common.specular_accum > 0.0 && i < prbNumRenderCube && i < MAX_PROBE; \
|
||||
i++) { \
|
||||
ClosureCubemapData cube = closure_cubemap_eval_init(i, cl_common); \
|
||||
if (cube.attenuation > 1e-8) { \
|
||||
CLOSURE_META_SUBROUTINE_DATA(cubemap_eval, cube, t0, t1, t2, t3); \
|
||||
/* Starts at 1 because 0 is world cubemap. */ \
|
||||
for (int i = 1; cl_common.specular_accum > 0.0 && i < prbNumRenderCube && i < MAX_PROBE; \
|
||||
i++) { \
|
||||
ClosureCubemapData cube = closure_cubemap_eval_init(i, cl_common); \
|
||||
if (cube.attenuation > 1e-8) { \
|
||||
CLOSURE_META_SUBROUTINE_DATA(cubemap_eval, cube, t0, t1, t2, t3); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/* Starts at 1 because 0 is world irradiance. */ \
|
||||
for (int i = 1; cl_common.diffuse_accum > 0.0 && i < prbNumRenderGrid && i < MAX_GRID; i++) { \
|
||||
ClosureGridData grid = closure_grid_eval_init(i, cl_common); \
|
||||
if (grid.attenuation > 1e-8) { \
|
||||
CLOSURE_META_SUBROUTINE_DATA(grid_eval, grid, t0, t1, t2, t3); \
|
||||
/* Starts at 1 because 0 is world irradiance. */ \
|
||||
for (int i = 1; cl_common.diffuse_accum > 0.0 && i < prbNumRenderGrid && i < MAX_GRID; \
|
||||
i++) { \
|
||||
ClosureGridData grid = closure_grid_eval_init(i, cl_common); \
|
||||
if (grid.attenuation > 1e-8) { \
|
||||
CLOSURE_META_SUBROUTINE_DATA(grid_eval, grid, t0, t1, t2, t3); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
CLOSURE_META_SUBROUTINE(indirect_end, t0, t1, t2, t3); \
|
||||
CLOSURE_META_SUBROUTINE(indirect_end, t0, t1, t2, t3); \
|
||||
\
|
||||
ClosurePlanarData planar = closure_planar_eval_init(cl_common); \
|
||||
if (planar.attenuation > 1e-8) { \
|
||||
CLOSURE_META_SUBROUTINE_DATA(planar_eval, planar, t0, t1, t2, t3); \
|
||||
} \
|
||||
\
|
||||
for (int i = 0; i < laNumLight && i < MAX_LIGHT; i++) { \
|
||||
ClosureLightData light = closure_light_eval_init(cl_common, i); \
|
||||
if (light.vis > 1e-8) { \
|
||||
CLOSURE_META_SUBROUTINE_DATA(light_eval, light, t0, t1, t2, t3); \
|
||||
ClosurePlanarData planar = closure_planar_eval_init(cl_common); \
|
||||
if (planar.attenuation > 1e-8) { \
|
||||
CLOSURE_META_SUBROUTINE_DATA(planar_eval, planar, t0, t1, t2, t3); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
CLOSURE_META_SUBROUTINE(eval_end, t0, t1, t2, t3); \
|
||||
}
|
||||
for (int i = 0; i < laNumLight && i < MAX_LIGHT; i++) { \
|
||||
ClosureLightData light = closure_light_eval_init(cl_common, i); \
|
||||
if (light.vis > 1e-8) { \
|
||||
CLOSURE_META_SUBROUTINE_DATA(light_eval, light, t0, t1, t2, t3); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
CLOSURE_META_SUBROUTINE(eval_end, t0, t1, t2, t3); \
|
||||
}
|
||||
|
||||
#else
|
||||
/* Inputs are inout so that callers can get the final inputs used for evaluation. */
|
||||
# define CLOSURE_EVAL_FUNCTION_DECLARE(name, t0, t1, t2, t3) \
|
||||
void closure_##name##_eval(ClosureInputCommon in_common, \
|
||||
inout ClosureInput##t0 in_##t0##_0, \
|
||||
inout ClosureInput##t1 in_##t1##_1, \
|
||||
inout ClosureInput##t2 in_##t2##_2, \
|
||||
inout ClosureInput##t3 in_##t3##_3, \
|
||||
out ClosureOutput##t0 out_##t0##_0, \
|
||||
out ClosureOutput##t1 out_##t1##_1, \
|
||||
out ClosureOutput##t2 out_##t2##_2, \
|
||||
out ClosureOutput##t3 out_##t3##_3) \
|
||||
{ \
|
||||
CLOSURE_EVAL_DECLARE(t0, t1, t2, t3); \
|
||||
}
|
||||
#endif
|
||||
|
||||
#define CLOSURE_EVAL_FUNCTION(name, t0, t1, t2, t3) \
|
||||
closure_##name##_eval(in_common, \
|
||||
|
|
|
@ -136,7 +136,7 @@ const float layer_offset_fg = 0.5 + 1.0;
|
|||
/* Extra offset for convolution layers to avoid light leaking from background. */
|
||||
const float layer_offset = 0.5 + 0.5;
|
||||
|
||||
#define DOF_MAX_SLIGHT_FOCUS_RADIUS 5
|
||||
#define DOF_MAX_SLIGHT_FOCUS_RADIUS 16
|
||||
|
||||
float dof_layer_weight(float coc, const bool is_foreground)
|
||||
{
|
||||
|
|
|
@ -40,7 +40,7 @@ void dof_slight_focus_gather(float radius, out vec4 out_color, out float out_wei
|
|||
DofGatherData fg_accum = GATHER_DATA_INIT;
|
||||
DofGatherData bg_accum = GATHER_DATA_INIT;
|
||||
|
||||
int i_radius = clamp(int(radius), 0, int(layer_threshold));
|
||||
int i_radius = clamp(int(radius + 0.5), 0, int(layer_threshold));
|
||||
const int resolve_ring_density = DOF_SLIGHT_FOCUS_DENSITY;
|
||||
ivec2 texel = ivec2(gl_FragCoord.xy);
|
||||
|
||||
|
|
|
@ -1043,7 +1043,7 @@ static void draw_bone_update_disp_matrix_default(EditBone *eBone, bPoseChannel *
|
|||
bone_mat = pchan->pose_mat;
|
||||
disp_mat = pchan->disp_mat;
|
||||
disp_tail_mat = pchan->disp_tail_mat;
|
||||
mul_v3_v3fl(bone_scale, pchan->custom_scale_xyz, pchan->bone->length);
|
||||
copy_v3_fl(bone_scale, pchan->bone->length);
|
||||
}
|
||||
else {
|
||||
eBone->length = len_v3v3(eBone->tail, eBone->head);
|
||||
|
@ -1271,9 +1271,9 @@ static void draw_bone_update_disp_matrix_custom(bPoseChannel *pchan)
|
|||
|
||||
copy_m4_m4(disp_mat, bone_mat);
|
||||
translate_m4(disp_mat,
|
||||
pchan->custom_translation[0],
|
||||
pchan->custom_translation[1],
|
||||
pchan->custom_translation[2]);
|
||||
pchan->custom_translation[0],
|
||||
pchan->custom_translation[1],
|
||||
pchan->custom_translation[2]);
|
||||
mul_m4_m4m3(disp_mat, disp_mat, rot_mat);
|
||||
rescale_m4(disp_mat, bone_scale);
|
||||
copy_m4_m4(disp_tail_mat, disp_mat);
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/* Apache License, Version 2.0 */
|
||||
|
||||
#include "draw_testing.hh"
|
||||
|
||||
#include "GPU_shader.h"
|
||||
|
||||
#include "draw_manager_testing.h"
|
||||
|
||||
namespace blender::draw {
|
||||
|
||||
/* Base class for draw test cases. It will setup and tear down the GPU part around each test. */
|
||||
void DrawTest::SetUp()
|
||||
{
|
||||
GPUTest::SetUp();
|
||||
DRW_draw_state_init_gtests(GPU_SHADER_CFG_DEFAULT);
|
||||
}
|
||||
|
||||
} // namespace blender::draw
|
|
@ -0,0 +1,13 @@
|
|||
/* Apache License, Version 2.0 */
|
||||
|
||||
#include "gpu_testing.hh"
|
||||
|
||||
namespace blender::draw {
|
||||
|
||||
/* Base class for draw test cases. It will setup and tear down the GPU part around each test. */
|
||||
class DrawTest : public blender::gpu::GPUTest {
|
||||
public:
|
||||
void SetUp() override;
|
||||
};
|
||||
|
||||
} // namespace blender::draw
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
#include "testing/testing.h"
|
||||
|
||||
#include "draw_testing.hh"
|
||||
#include "intern/draw_manager_testing.h"
|
||||
|
||||
#include "GPU_context.h"
|
||||
#include "GPU_init_exit.h"
|
||||
#include "GPU_shader.h"
|
||||
#include "gpu_testing.hh"
|
||||
|
||||
#include "engines/eevee/eevee_private.h"
|
||||
#include "engines/gpencil/gpencil_engine.h"
|
||||
|
@ -17,19 +17,9 @@
|
|||
|
||||
namespace blender::draw {
|
||||
|
||||
/* Base class for draw test cases. It will setup and tear down the GPU part around each test. */
|
||||
class DrawTest : public blender::gpu::GPUTest {
|
||||
void SetUp() override
|
||||
{
|
||||
GPUTest::SetUp();
|
||||
DRW_draw_state_init_gtests(GPU_SHADER_CFG_DEFAULT);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DrawTest, workbench_glsl_shaders)
|
||||
{
|
||||
workbench_shader_library_ensure();
|
||||
DRW_draw_state_init_gtests(GPU_SHADER_CFG_DEFAULT);
|
||||
|
||||
const int MAX_WPD = 6;
|
||||
WORKBENCH_PrivateData wpds[MAX_WPD];
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
set(INC
|
||||
../include
|
||||
../../blenfont
|
||||
../../blenkernel
|
||||
../../blenlib
|
||||
../../blentranslation
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include "DNA_armature_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_vec_types.h"
|
||||
|
||||
#include "BKE_fcurve.h"
|
||||
#include "BKE_nla.h"
|
||||
|
@ -50,15 +51,28 @@
|
|||
#include "WM_types.h"
|
||||
|
||||
#include "UI_interface.h"
|
||||
#include "UI_resources.h"
|
||||
|
||||
#include "ED_armature.h"
|
||||
#include "ED_keyframes_draw.h"
|
||||
#include "ED_markers.h"
|
||||
#include "ED_numinput.h"
|
||||
#include "ED_screen.h"
|
||||
#include "ED_space_api.h"
|
||||
|
||||
#include "GPU_immediate.h"
|
||||
#include "GPU_immediate_util.h"
|
||||
#include "GPU_matrix.h"
|
||||
#include "GPU_state.h"
|
||||
|
||||
#include "armature_intern.h"
|
||||
|
||||
#include "BLF_api.h"
|
||||
|
||||
/* Pixel distance from 0% to 100%. */
|
||||
#define SLIDE_PIXEL_DISTANCE (300 * U.pixelsize)
|
||||
#define OVERSHOOT_RANGE_DELTA 0.2f
|
||||
|
||||
/* **************************************************** */
|
||||
/* == POSE 'SLIDING' TOOLS ==
|
||||
*
|
||||
|
@ -110,15 +124,36 @@ typedef struct tPoseSlideOp {
|
|||
/** unused for now, but can later get used for storing runtime settings.... */
|
||||
short flag;
|
||||
|
||||
/* Store overlay settings when invoking the operator. Bones will be temporarily hidden. */
|
||||
int overlay_flag;
|
||||
|
||||
/** which transforms/channels are affected (ePoseSlide_Channels) */
|
||||
short channels;
|
||||
/** axis-limits for transforms (ePoseSlide_AxisLock) */
|
||||
short axislock;
|
||||
|
||||
/** 0-1 value for determining the influence of whatever is relevant */
|
||||
/* Allow overshoot or clamp between 0% and 100%. */
|
||||
bool overshoot;
|
||||
|
||||
/* Reduces percentage delta from mouse movement. */
|
||||
bool precision;
|
||||
|
||||
/* Move percentage in 10% steps. */
|
||||
bool increments;
|
||||
|
||||
/* Draw callback handler. */
|
||||
void *draw_handle;
|
||||
|
||||
/* Accumulative, unclamped and unrounded percentage. */
|
||||
float raw_percentage;
|
||||
|
||||
/* 0-1 value for determining the influence of whatever is relevant. */
|
||||
float percentage;
|
||||
|
||||
/** numeric input */
|
||||
/* Last cursor position in screen space used for mouse movement delta calculation. */
|
||||
int last_cursor_x;
|
||||
|
||||
/* Numeric input. */
|
||||
NumInput num;
|
||||
|
||||
struct tPoseSlideObject *ob_data_array;
|
||||
|
@ -187,6 +222,240 @@ static const EnumPropertyItem prop_axis_lock_types[] = {
|
|||
|
||||
/* ------------------------------------ */
|
||||
|
||||
static void draw_overshoot_triangle(const uint8_t color[4],
|
||||
const bool facing_right,
|
||||
const float x,
|
||||
const float y)
|
||||
{
|
||||
const uint shdr_pos_2d = GPU_vertformat_attr_add(
|
||||
immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
|
||||
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
|
||||
GPU_blend(GPU_BLEND_ALPHA);
|
||||
GPU_polygon_smooth(true);
|
||||
immUniformColor3ubvAlpha(color, 225);
|
||||
const float triangle_side_length = facing_right ? 6 * U.pixelsize : -6 * U.pixelsize;
|
||||
const float triangle_offset = facing_right ? 2 * U.pixelsize : -2 * U.pixelsize;
|
||||
|
||||
immBegin(GPU_PRIM_TRIS, 3);
|
||||
immVertex2f(shdr_pos_2d, x + triangle_offset + triangle_side_length, y);
|
||||
immVertex2f(shdr_pos_2d, x + triangle_offset, y + triangle_side_length / 2);
|
||||
immVertex2f(shdr_pos_2d, x + triangle_offset, y - triangle_side_length / 2);
|
||||
immEnd();
|
||||
|
||||
GPU_polygon_smooth(false);
|
||||
GPU_blend(GPU_BLEND_NONE);
|
||||
immUnbindProgram();
|
||||
}
|
||||
|
||||
static void draw_ticks(const float start_percentage,
|
||||
const float end_percentage,
|
||||
const struct vec2f line_start,
|
||||
const float base_tick_height,
|
||||
const float line_width,
|
||||
const uint8_t color_overshoot[4],
|
||||
const uint8_t color_line[4])
|
||||
{
|
||||
/* Use percentage represented as 0-100 int to avoid floating point precision problems. */
|
||||
const int tick_increment = 10;
|
||||
|
||||
/* Round initial_tick_percentage up to the next tick_increment. */
|
||||
int tick_percentage = ceil((start_percentage * 100) / tick_increment) * tick_increment;
|
||||
float tick_height = base_tick_height;
|
||||
|
||||
while (tick_percentage <= (int)(end_percentage * 100)) {
|
||||
/* Different ticks have different heights. Multiples of 100% are the tallest, 50% is a bit
|
||||
* smaller and the rest is the minimum size. */
|
||||
if (tick_percentage % 100 == 0) {
|
||||
tick_height = base_tick_height;
|
||||
}
|
||||
else if (tick_percentage % 50 == 0) {
|
||||
tick_height = base_tick_height * 0.8;
|
||||
}
|
||||
else {
|
||||
tick_height = base_tick_height * 0.5;
|
||||
}
|
||||
|
||||
const float x = line_start.x +
|
||||
(((float)tick_percentage / 100) - start_percentage) * SLIDE_PIXEL_DISTANCE;
|
||||
const struct rctf tick_rect = {.xmin = x - (line_width / 2),
|
||||
.xmax = x + (line_width / 2),
|
||||
.ymin = line_start.y - (tick_height / 2),
|
||||
.ymax = line_start.y + (tick_height / 2)};
|
||||
|
||||
if (tick_percentage < 0 || tick_percentage > 100) {
|
||||
UI_draw_roundbox_3ub_alpha(&tick_rect, true, 1, color_overshoot, 255);
|
||||
}
|
||||
else {
|
||||
UI_draw_roundbox_3ub_alpha(&tick_rect, true, 1, color_line, 255);
|
||||
}
|
||||
tick_percentage += tick_increment;
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_main_line(const struct rctf main_line_rect,
|
||||
const float percentage,
|
||||
const bool overshoot,
|
||||
const uint8_t color_overshoot[4],
|
||||
const uint8_t color_line[4])
|
||||
{
|
||||
if (overshoot) {
|
||||
/* In overshoot mode, draw the 0-100% range differently to provide a visual reference. */
|
||||
const float line_zero_percent = main_line_rect.xmin -
|
||||
((percentage - 0.5f - OVERSHOOT_RANGE_DELTA) *
|
||||
SLIDE_PIXEL_DISTANCE);
|
||||
|
||||
const float clamped_line_zero_percent = clamp_f(
|
||||
line_zero_percent, main_line_rect.xmin, main_line_rect.xmax);
|
||||
const float clamped_line_hundred_percent = clamp_f(
|
||||
line_zero_percent + SLIDE_PIXEL_DISTANCE, main_line_rect.xmin, main_line_rect.xmax);
|
||||
|
||||
const struct rctf left_overshoot_line_rect = {.xmin = main_line_rect.xmin,
|
||||
.xmax = clamped_line_zero_percent,
|
||||
.ymin = main_line_rect.ymin,
|
||||
.ymax = main_line_rect.ymax};
|
||||
const struct rctf right_overshoot_line_rect = {.xmin = clamped_line_hundred_percent,
|
||||
.xmax = main_line_rect.xmax,
|
||||
.ymin = main_line_rect.ymin,
|
||||
.ymax = main_line_rect.ymax};
|
||||
UI_draw_roundbox_3ub_alpha(&left_overshoot_line_rect, true, 0, color_overshoot, 255);
|
||||
UI_draw_roundbox_3ub_alpha(&right_overshoot_line_rect, true, 0, color_overshoot, 255);
|
||||
|
||||
const struct rctf non_overshoot_line_rect = {.xmin = clamped_line_zero_percent,
|
||||
.xmax = clamped_line_hundred_percent,
|
||||
.ymin = main_line_rect.ymin,
|
||||
.ymax = main_line_rect.ymax};
|
||||
UI_draw_roundbox_3ub_alpha(&non_overshoot_line_rect, true, 0, color_line, 255);
|
||||
}
|
||||
else {
|
||||
UI_draw_roundbox_3ub_alpha(&main_line_rect, true, 0, color_line, 255);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_backdrop(const int fontid,
|
||||
const struct rctf main_line_rect,
|
||||
const float color_bg[4],
|
||||
const short region_y_size,
|
||||
const float base_tick_height)
|
||||
{
|
||||
float string_pixel_size[2];
|
||||
const char *percentage_placeholder = "000%%";
|
||||
BLF_width_and_height(fontid,
|
||||
percentage_placeholder,
|
||||
sizeof(percentage_placeholder),
|
||||
&string_pixel_size[0],
|
||||
&string_pixel_size[1]);
|
||||
const struct vec2f pad = {.x = (region_y_size - base_tick_height) / 2, .y = 2.0f * U.pixelsize};
|
||||
const struct rctf backdrop_rect = {.xmin = main_line_rect.xmin - string_pixel_size[0] - pad.x,
|
||||
.xmax = main_line_rect.xmax + pad.x,
|
||||
.ymin = pad.y,
|
||||
.ymax = region_y_size - pad.y};
|
||||
UI_draw_roundbox_aa(&backdrop_rect, true, 4.0f, color_bg);
|
||||
}
|
||||
|
||||
/* Draw an on screen Slider for a Pose Slide Operator. */
|
||||
static void pose_slide_draw_2d_slider(const struct bContext *UNUSED(C), ARegion *region, void *arg)
|
||||
{
|
||||
tPoseSlideOp *pso = arg;
|
||||
|
||||
/* Only draw in region from which the Operator was started. */
|
||||
if (region != pso->region) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t color_text[4];
|
||||
uint8_t color_line[4];
|
||||
uint8_t color_handle[4];
|
||||
uint8_t color_overshoot[4];
|
||||
float color_bg[4];
|
||||
|
||||
/* Get theme colors. */
|
||||
UI_GetThemeColor4ubv(TH_TEXT, color_text);
|
||||
UI_GetThemeColor4ubv(TH_TEXT, color_line);
|
||||
UI_GetThemeColor4ubv(TH_TEXT, color_overshoot);
|
||||
UI_GetThemeColor4ubv(TH_ACTIVE, color_handle);
|
||||
UI_GetThemeColor3fv(TH_BACK, color_bg);
|
||||
|
||||
color_bg[3] = 0.5f;
|
||||
color_overshoot[0] = color_overshoot[0] * 0.7;
|
||||
color_overshoot[1] = color_overshoot[1] * 0.7;
|
||||
color_overshoot[2] = color_overshoot[2] * 0.7;
|
||||
|
||||
/* Get the default font. */
|
||||
const uiStyle *style = UI_style_get();
|
||||
const uiFontStyle *fstyle = &style->widget;
|
||||
const int fontid = fstyle->uifont_id;
|
||||
BLF_color3ubv(fontid, color_text);
|
||||
BLF_rotation(fontid, 0.0f);
|
||||
|
||||
const float line_width = 1.5 * U.pixelsize;
|
||||
const float base_tick_height = 12.0 * U.pixelsize;
|
||||
const float line_y = region->winy / 2;
|
||||
|
||||
struct rctf main_line_rect = {.xmin = (region->winx / 2) - (SLIDE_PIXEL_DISTANCE / 2),
|
||||
.xmax = (region->winx / 2) + (SLIDE_PIXEL_DISTANCE / 2),
|
||||
.ymin = line_y - line_width / 2,
|
||||
.ymax = line_y + line_width / 2};
|
||||
float line_start_percentage = 0;
|
||||
int handle_pos_x = main_line_rect.xmin + SLIDE_PIXEL_DISTANCE * pso->percentage;
|
||||
|
||||
if (pso->overshoot) {
|
||||
main_line_rect.xmin = main_line_rect.xmin - SLIDE_PIXEL_DISTANCE * OVERSHOOT_RANGE_DELTA;
|
||||
main_line_rect.xmax = main_line_rect.xmax + SLIDE_PIXEL_DISTANCE * OVERSHOOT_RANGE_DELTA;
|
||||
line_start_percentage = pso->percentage - 0.5f - OVERSHOOT_RANGE_DELTA;
|
||||
handle_pos_x = region->winx / 2;
|
||||
}
|
||||
|
||||
draw_backdrop(fontid, main_line_rect, color_bg, pso->region->winy, base_tick_height);
|
||||
|
||||
draw_main_line(main_line_rect, pso->percentage, pso->overshoot, color_overshoot, color_line);
|
||||
|
||||
const float percentage_range = pso->overshoot ? 1 + OVERSHOOT_RANGE_DELTA * 2 : 1;
|
||||
const struct vec2f line_start_position = {.x = main_line_rect.xmin, .y = line_y};
|
||||
draw_ticks(line_start_percentage,
|
||||
line_start_percentage + percentage_range,
|
||||
line_start_position,
|
||||
base_tick_height,
|
||||
line_width,
|
||||
color_overshoot,
|
||||
color_line);
|
||||
|
||||
/* Draw triangles at the ends of the line in overshoot mode to indicate direction of 0-100%
|
||||
* range.*/
|
||||
if (pso->overshoot) {
|
||||
if (pso->percentage > 1 + OVERSHOOT_RANGE_DELTA + 0.5) {
|
||||
draw_overshoot_triangle(color_line, false, main_line_rect.xmin, line_y);
|
||||
}
|
||||
if (pso->percentage < 0 - OVERSHOOT_RANGE_DELTA - 0.5) {
|
||||
draw_overshoot_triangle(color_line, true, main_line_rect.xmax, line_y);
|
||||
}
|
||||
}
|
||||
|
||||
char percentage_string[256];
|
||||
|
||||
/* Draw handle indicating current percentage. */
|
||||
const struct rctf handle_rect = {.xmin = handle_pos_x - (line_width),
|
||||
.xmax = handle_pos_x + (line_width),
|
||||
.ymin = line_y - (base_tick_height / 2),
|
||||
.ymax = line_y + (base_tick_height / 2)};
|
||||
|
||||
UI_draw_roundbox_3ub_alpha(&handle_rect, true, 1, color_handle, 255);
|
||||
BLI_snprintf(percentage_string, sizeof(percentage_string), "%.0f%%", pso->percentage * 100);
|
||||
|
||||
/* Draw percentage string. */
|
||||
float percentage_pixel_size[2];
|
||||
BLF_width_and_height(fontid,
|
||||
percentage_string,
|
||||
sizeof(percentage_string),
|
||||
&percentage_pixel_size[0],
|
||||
&percentage_pixel_size[1]);
|
||||
|
||||
BLF_position(fontid,
|
||||
main_line_rect.xmin - 24.0 * U.pixelsize - percentage_pixel_size[0] / 2,
|
||||
(region->winy / 2) - percentage_pixel_size[1] / 2,
|
||||
0.0f);
|
||||
BLF_draw(fontid, percentage_string, sizeof(percentage_string));
|
||||
}
|
||||
|
||||
/* operator init */
|
||||
static int pose_slide_init(bContext *C, wmOperator *op, ePoseSlide_Modes mode)
|
||||
{
|
||||
|
@ -205,6 +474,7 @@ static int pose_slide_init(bContext *C, wmOperator *op, ePoseSlide_Modes mode)
|
|||
|
||||
/* set range info from property values - these may get overridden for the invoke() */
|
||||
pso->percentage = RNA_float_get(op->ptr, "percentage");
|
||||
pso->raw_percentage = pso->percentage;
|
||||
pso->prevFrame = RNA_int_get(op->ptr, "prev_frame");
|
||||
pso->nextFrame = RNA_int_get(op->ptr, "next_frame");
|
||||
|
||||
|
@ -257,6 +527,17 @@ static int pose_slide_init(bContext *C, wmOperator *op, ePoseSlide_Modes mode)
|
|||
pso->num.val_flag[0] |= NUM_NO_NEGATIVE;
|
||||
pso->num.unit_type[0] = B_UNIT_NONE; /* percentages don't have any units... */
|
||||
|
||||
/* Register UI drawing callback. */
|
||||
/* pso->draw_handle = ED_region_draw_cb_activate(
|
||||
pso->region->type, pose_slide_draw_2d_slider, pso, REGION_DRAW_POST_PIXEL); */
|
||||
LISTBASE_FOREACH (ARegion *, region, &pso->area->regionbase) {
|
||||
if (region->regiontype == RGN_TYPE_HEADER) {
|
||||
pso->region = region;
|
||||
pso->draw_handle = ED_region_draw_cb_activate(
|
||||
region->type, pose_slide_draw_2d_slider, pso, REGION_DRAW_POST_PIXEL);
|
||||
}
|
||||
}
|
||||
|
||||
/* return status is whether we've got all the data we were requested to get */
|
||||
return 1;
|
||||
}
|
||||
|
@ -266,6 +547,13 @@ static void pose_slide_exit(wmOperator *op)
|
|||
{
|
||||
tPoseSlideOp *pso = op->customdata;
|
||||
|
||||
/* Hide Bone Overlay. */
|
||||
View3D *v3d = pso->area->spacedata.first;
|
||||
v3d->overlay.flag = pso->overlay_flag;
|
||||
|
||||
/* Remove UI drawing callback. */
|
||||
ED_region_draw_cb_exit(pso->region->type, pso->draw_handle);
|
||||
|
||||
/* if data exists, clear its data and exit */
|
||||
if (pso) {
|
||||
/* free the temp pchan links and their data */
|
||||
|
@ -602,7 +890,7 @@ static void pose_slide_apply_quat(tPoseSlideOp *pso, tPChanFCurveLink *pfl)
|
|||
|
||||
static void pose_slide_rest_pose_apply_vec3(tPoseSlideOp *pso, float vec[3], float default_value)
|
||||
{
|
||||
/* We only slide to the rest pose. So only use the default rest pose value */
|
||||
/* We only slide to the rest pose. So only use the default rest pose value. */
|
||||
const int lock = pso->axislock;
|
||||
for (int idx = 0; idx < 3; idx++) {
|
||||
if ((lock == 0) || ((lock & PS_LOCK_X) && (idx == 0)) || ((lock & PS_LOCK_Y) && (idx == 1)) ||
|
||||
|
@ -621,7 +909,7 @@ static void pose_slide_rest_pose_apply_vec3(tPoseSlideOp *pso, float vec[3], flo
|
|||
|
||||
static void pose_slide_rest_pose_apply_other_rot(tPoseSlideOp *pso, float vec[4], bool quat)
|
||||
{
|
||||
/* We only slide to the rest pose. So only use the default rest pose value */
|
||||
/* We only slide to the rest pose. So only use the default rest pose value. */
|
||||
float default_values[] = {1.0f, 0.0f, 0.0f, 0.0f};
|
||||
if (!quat) {
|
||||
/* Axis Angle */
|
||||
|
@ -789,14 +1077,18 @@ static void pose_slide_reset(tPoseSlideOp *pso)
|
|||
|
||||
/* ------------------------------------ */
|
||||
|
||||
/* draw percentage indicator in header */
|
||||
/* Draw percentage indicator in workspace footer. */
|
||||
/* TODO: Include hints about locks here... */
|
||||
static void pose_slide_draw_status(tPoseSlideOp *pso)
|
||||
static void pose_slide_draw_status(bContext *C, tPoseSlideOp *pso)
|
||||
{
|
||||
char status_str[UI_MAX_DRAW_STR];
|
||||
char limits_str[UI_MAX_DRAW_STR];
|
||||
char axis_str[50];
|
||||
char mode_str[32];
|
||||
char overshoot_str[50];
|
||||
char precision_str[50];
|
||||
char increments_str[50];
|
||||
char bone_vis_str[50];
|
||||
|
||||
switch (pso->mode) {
|
||||
case POSESLIDE_PUSH:
|
||||
|
@ -871,25 +1163,51 @@ static void pose_slide_draw_status(tPoseSlideOp *pso)
|
|||
break;
|
||||
}
|
||||
|
||||
if (pso->overshoot) {
|
||||
BLI_strncpy(overshoot_str, TIP_("[E] - Disable overshoot"), sizeof(overshoot_str));
|
||||
}
|
||||
else {
|
||||
BLI_strncpy(overshoot_str, TIP_("E - Enable overshoot"), sizeof(overshoot_str));
|
||||
}
|
||||
|
||||
if (pso->precision) {
|
||||
BLI_strncpy(precision_str, TIP_("[Shift] - Precision active"), sizeof(precision_str));
|
||||
}
|
||||
else {
|
||||
BLI_strncpy(precision_str, TIP_("Shift - Hold for precision"), sizeof(precision_str));
|
||||
}
|
||||
|
||||
if (pso->increments) {
|
||||
BLI_strncpy(increments_str, TIP_("[Ctrl] - Increments active"), sizeof(increments_str));
|
||||
}
|
||||
else {
|
||||
BLI_strncpy(increments_str, TIP_("Ctrl - Hold for 10% increments"), sizeof(increments_str));
|
||||
}
|
||||
|
||||
BLI_strncpy(bone_vis_str, TIP_("[H] - Toggle bone visibility"), sizeof(increments_str));
|
||||
|
||||
if (hasNumInput(&pso->num)) {
|
||||
Scene *scene = pso->scene;
|
||||
char str_ofs[NUM_STR_REP_LEN];
|
||||
char str_offs[NUM_STR_REP_LEN];
|
||||
|
||||
outputNumInput(&pso->num, str_ofs, &scene->unit);
|
||||
outputNumInput(&pso->num, str_offs, &scene->unit);
|
||||
|
||||
BLI_snprintf(
|
||||
status_str, sizeof(status_str), "%s: %s | %s", mode_str, str_ofs, limits_str);
|
||||
BLI_snprintf(status_str, sizeof(status_str), "%s: %s | %s", mode_str, str_offs, limits_str);
|
||||
}
|
||||
else {
|
||||
BLI_snprintf(status_str,
|
||||
sizeof(status_str),
|
||||
"%s: %d %% | %s",
|
||||
mode_str,
|
||||
(int)(pso->percentage * 100.0f),
|
||||
limits_str);
|
||||
sizeof(status_str),
|
||||
"%s: %s | %s | %s | %s | %s",
|
||||
mode_str,
|
||||
limits_str,
|
||||
overshoot_str,
|
||||
precision_str,
|
||||
increments_str,
|
||||
bone_vis_str);
|
||||
}
|
||||
|
||||
ED_area_status_text(pso->area, status_str);
|
||||
ED_workspace_status_text(C, status_str);
|
||||
ED_area_status_text(pso->area, "");
|
||||
}
|
||||
|
||||
/* common code for invoke() methods */
|
||||
|
@ -975,21 +1293,40 @@ static int pose_slide_invoke_common(bContext *C, wmOperator *op, tPoseSlideOp *p
|
|||
WM_cursor_modal_set(win, WM_CURSOR_EW_SCROLL);
|
||||
|
||||
/* header print */
|
||||
pose_slide_draw_status(pso);
|
||||
pose_slide_draw_status(C, pso);
|
||||
|
||||
/* add a modal handler for this operator */
|
||||
WM_event_add_modal_handler(C, op);
|
||||
|
||||
/* Hide Bone Overlay. */
|
||||
View3D *v3d = pso->area->spacedata.first;
|
||||
pso->overlay_flag = v3d->overlay.flag;
|
||||
v3d->overlay.flag |= V3D_OVERLAY_HIDE_BONES;
|
||||
|
||||
return OPERATOR_RUNNING_MODAL;
|
||||
}
|
||||
|
||||
/* calculate percentage based on position of mouse (we only use x-axis for now.
|
||||
* since this is more convenient for users to do), and store new percentage value
|
||||
/* Calculate percentage based on mouse movement, clamp or round to increments if
|
||||
* enabled by the user. Store the new percentage value.
|
||||
*/
|
||||
static void pose_slide_mouse_update_percentage(tPoseSlideOp *pso,
|
||||
wmOperator *op,
|
||||
const wmEvent *event)
|
||||
{
|
||||
pso->percentage = (event->x - pso->region->winrct.xmin) / ((float)pso->region->winx);
|
||||
const float percentage_delta = (event->x - pso->last_cursor_x) / ((float)(SLIDE_PIXEL_DISTANCE));
|
||||
/* Reduced percentage delta in precision mode (shift held). */
|
||||
pso->raw_percentage += pso->precision ? (percentage_delta / 8) : percentage_delta;
|
||||
pso->percentage = pso->raw_percentage;
|
||||
pso->last_cursor_x = event->x;
|
||||
|
||||
if (!pso->overshoot) {
|
||||
pso->percentage = clamp_f(pso->percentage, 0, 1);
|
||||
}
|
||||
|
||||
if (pso->increments) {
|
||||
pso->percentage = round(pso->percentage * 10) / 10;
|
||||
}
|
||||
|
||||
RNA_float_set(op->ptr, "percentage", pso->percentage);
|
||||
}
|
||||
|
||||
|
@ -1056,9 +1393,13 @@ static int pose_slide_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|||
case EVT_PADENTER: {
|
||||
if (event->val == KM_PRESS) {
|
||||
/* return to normal cursor and header status */
|
||||
ED_workspace_status_text(C, NULL);
|
||||
ED_area_status_text(pso->area, NULL);
|
||||
WM_cursor_modal_restore(win);
|
||||
|
||||
/* Depsgraph updates + redraws. Redraw needed to remove UI. */
|
||||
pose_slide_refresh(C, pso);
|
||||
|
||||
/* insert keyframes as required... */
|
||||
pose_slide_autoKeyframe(C, pso);
|
||||
pose_slide_exit(op);
|
||||
|
@ -1073,13 +1414,14 @@ static int pose_slide_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|||
case RIGHTMOUSE: {
|
||||
if (event->val == KM_PRESS) {
|
||||
/* return to normal cursor and header status */
|
||||
ED_workspace_status_text(C, NULL);
|
||||
ED_area_status_text(pso->area, NULL);
|
||||
WM_cursor_modal_restore(win);
|
||||
|
||||
/* reset transforms back to original state */
|
||||
pose_slide_reset(pso);
|
||||
|
||||
/* depsgraph updates + redraws */
|
||||
/* Depsgraph updates + redraws.*/
|
||||
pose_slide_refresh(C, pso);
|
||||
|
||||
/* clean up temp data */
|
||||
|
@ -1178,10 +1520,58 @@ static int pose_slide_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|||
break;
|
||||
}
|
||||
|
||||
/* Overshoot. */
|
||||
case EVT_EKEY: {
|
||||
pso->overshoot = !pso->overshoot;
|
||||
do_pose_update = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Precision mode. */
|
||||
case EVT_LEFTSHIFTKEY:
|
||||
case EVT_RIGHTSHIFTKEY: {
|
||||
pso->precision = true;
|
||||
do_pose_update = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Increments mode. */
|
||||
case EVT_LEFTCTRLKEY:
|
||||
case EVT_RIGHTCTRLKEY: {
|
||||
pso->increments = true;
|
||||
do_pose_update = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Toggle Bone visibility. */
|
||||
case EVT_HKEY: {
|
||||
View3D *v3d = pso->area->spacedata.first;
|
||||
v3d->overlay.flag ^= V3D_OVERLAY_HIDE_BONES;
|
||||
}
|
||||
|
||||
default: /* Some other unhandled key... */
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Precision and stepping only active while button is held. */
|
||||
else if (event->val == KM_RELEASE) {
|
||||
switch (event->type) {
|
||||
case EVT_LEFTSHIFTKEY:
|
||||
case EVT_RIGHTSHIFTKEY: {
|
||||
pso->precision = false;
|
||||
do_pose_update = true;
|
||||
break;
|
||||
}
|
||||
case EVT_LEFTCTRLKEY:
|
||||
case EVT_RIGHTCTRLKEY: {
|
||||
pso->increments = false;
|
||||
do_pose_update = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* unhandled event - maybe it was some view manipulation? */
|
||||
/* allow to pass through */
|
||||
|
@ -1193,8 +1583,10 @@ static int pose_slide_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|||
/* Perform pose updates - in response to some user action
|
||||
* (e.g. pressing a key or moving the mouse). */
|
||||
if (do_pose_update) {
|
||||
pose_slide_mouse_update_percentage(pso, op, event);
|
||||
|
||||
/* update percentage indicator in header */
|
||||
pose_slide_draw_status(pso);
|
||||
pose_slide_draw_status(C, pso);
|
||||
|
||||
/* reset transforms (to avoid accumulation errors) */
|
||||
pose_slide_reset(pso);
|
||||
|
@ -1247,11 +1639,11 @@ static void pose_slide_opdef_properties(wmOperatorType *ot)
|
|||
PropertyRNA *prop;
|
||||
|
||||
prop = RNA_def_float_factor(ot->srna,
|
||||
"factor",
|
||||
"percentage",
|
||||
0.5f,
|
||||
0.0f,
|
||||
1.0f,
|
||||
"Factor",
|
||||
"Percentage",
|
||||
"Weighting factor for which keyframe is favored more",
|
||||
0.0,
|
||||
1.0);
|
||||
|
@ -1310,6 +1702,8 @@ static int pose_slide_push_invoke(bContext *C, wmOperator *op, const wmEvent *ev
|
|||
|
||||
pso = op->customdata;
|
||||
|
||||
pso->last_cursor_x = event->x;
|
||||
|
||||
/* Initialize percentage so that it won't pop on first mouse move. */
|
||||
pose_slide_mouse_update_percentage(pso, op, event);
|
||||
|
||||
|
@ -1349,7 +1743,7 @@ void POSE_OT_push(wmOperatorType *ot)
|
|||
ot->poll = ED_operator_posemode;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X;
|
||||
|
||||
/* Properties */
|
||||
pose_slide_opdef_properties(ot);
|
||||
|
@ -1370,6 +1764,8 @@ static int pose_slide_relax_invoke(bContext *C, wmOperator *op, const wmEvent *e
|
|||
|
||||
pso = op->customdata;
|
||||
|
||||
pso->last_cursor_x = event->x;
|
||||
|
||||
/* Initialize percentage so that it won't pop on first mouse move. */
|
||||
pose_slide_mouse_update_percentage(pso, op, event);
|
||||
|
||||
|
@ -1409,7 +1805,7 @@ void POSE_OT_relax(wmOperatorType *ot)
|
|||
ot->poll = ED_operator_posemode;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X;
|
||||
|
||||
/* Properties */
|
||||
pose_slide_opdef_properties(ot);
|
||||
|
@ -1429,6 +1825,8 @@ static int pose_slide_push_rest_invoke(bContext *C, wmOperator *op, const wmEven
|
|||
|
||||
pso = op->customdata;
|
||||
|
||||
pso->last_cursor_x = event->x;
|
||||
|
||||
/* Initialize percentage so that it won't pop on first mouse move. */
|
||||
pose_slide_mouse_update_percentage(pso, op, event);
|
||||
|
||||
|
@ -1468,7 +1866,7 @@ void POSE_OT_push_rest(wmOperatorType *ot)
|
|||
ot->poll = ED_operator_posemode;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X;
|
||||
|
||||
/* Properties */
|
||||
pose_slide_opdef_properties(ot);
|
||||
|
@ -1489,6 +1887,8 @@ static int pose_slide_relax_rest_invoke(bContext *C, wmOperator *op, const wmEve
|
|||
|
||||
pso = op->customdata;
|
||||
|
||||
pso->last_cursor_x = event->x;
|
||||
|
||||
/* Initialize percentage so that it won't pop on first mouse move. */
|
||||
pose_slide_mouse_update_percentage(pso, op, event);
|
||||
|
||||
|
@ -1528,7 +1928,7 @@ void POSE_OT_relax_rest(wmOperatorType *ot)
|
|||
ot->poll = ED_operator_posemode;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X;
|
||||
|
||||
/* Properties */
|
||||
pose_slide_opdef_properties(ot);
|
||||
|
@ -1549,6 +1949,8 @@ static int pose_slide_breakdown_invoke(bContext *C, wmOperator *op, const wmEven
|
|||
|
||||
pso = op->customdata;
|
||||
|
||||
pso->last_cursor_x = event->x;
|
||||
|
||||
/* Initialize percentage so that it won't pop on first mouse move. */
|
||||
pose_slide_mouse_update_percentage(pso, op, event);
|
||||
|
||||
|
@ -1588,7 +1990,7 @@ void POSE_OT_breakdown(wmOperatorType *ot)
|
|||
ot->poll = ED_operator_posemode;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X;
|
||||
|
||||
/* Properties */
|
||||
pose_slide_opdef_properties(ot);
|
||||
|
|
|
@ -1548,7 +1548,7 @@ static void annotation_paint_initstroke(tGPsdata *p,
|
|||
if (p->gpl == NULL) {
|
||||
/* tag for annotations */
|
||||
p->gpd->flag |= GP_DATA_ANNOTATIONS;
|
||||
p->gpl = BKE_gpencil_layer_addnew(p->gpd, DATA_("Note"), true);
|
||||
p->gpl = BKE_gpencil_layer_addnew(p->gpd, DATA_("Note"), true, false);
|
||||
|
||||
if (p->custom_color[3]) {
|
||||
copy_v3_v3(p->gpl->color, p->custom_color);
|
||||
|
|
|
@ -90,7 +90,7 @@ void ED_gpencil_create_blank(bContext *C, Object *ob, float UNUSED(mat[4][4]))
|
|||
ob->actcol = color_black + 1;
|
||||
|
||||
/* layers */
|
||||
bGPDlayer *layer = BKE_gpencil_layer_addnew(gpd, "GP_Layer", true);
|
||||
bGPDlayer *layer = BKE_gpencil_layer_addnew(gpd, "GP_Layer", true, false);
|
||||
|
||||
/* frames */
|
||||
BKE_gpencil_frame_addnew(layer, CFRA);
|
||||
|
|
|
@ -96,7 +96,7 @@ void ED_gpencil_create_lineart(bContext *C, Object *ob)
|
|||
ob->actcol = color_black + 1;
|
||||
|
||||
/* layers */
|
||||
bGPDlayer *lines = BKE_gpencil_layer_addnew(gpd, "Lines", true);
|
||||
bGPDlayer *lines = BKE_gpencil_layer_addnew(gpd, "Lines", true, false);
|
||||
|
||||
/* frames */
|
||||
BKE_gpencil_frame_addnew(lines, 0);
|
||||
|
|
|
@ -837,8 +837,8 @@ void ED_gpencil_create_monkey(bContext *C, Object *ob, float mat[4][4])
|
|||
|
||||
/* layers */
|
||||
/* NOTE: For now, we just add new layers, to make it easier to separate out old/new instances */
|
||||
bGPDlayer *Fills = BKE_gpencil_layer_addnew(gpd, "Fills", false);
|
||||
bGPDlayer *Lines = BKE_gpencil_layer_addnew(gpd, "Lines", true);
|
||||
bGPDlayer *Fills = BKE_gpencil_layer_addnew(gpd, "Fills", false, false);
|
||||
bGPDlayer *Lines = BKE_gpencil_layer_addnew(gpd, "Lines", true, false);
|
||||
|
||||
/* frames */
|
||||
/* NOTE: No need to check for existing, as this will take care of it for us */
|
||||
|
|
|
@ -225,8 +225,8 @@ void ED_gpencil_create_stroke(bContext *C, Object *ob, float mat[4][4])
|
|||
ob->actcol = color_black + 1;
|
||||
|
||||
/* layers */
|
||||
bGPDlayer *colors = BKE_gpencil_layer_addnew(gpd, "Colors", false);
|
||||
bGPDlayer *lines = BKE_gpencil_layer_addnew(gpd, "Lines", true);
|
||||
bGPDlayer *colors = BKE_gpencil_layer_addnew(gpd, "Colors", false, false);
|
||||
bGPDlayer *lines = BKE_gpencil_layer_addnew(gpd, "Lines", true, false);
|
||||
|
||||
/* frames */
|
||||
bGPDframe *frame_color = BKE_gpencil_frame_addnew(colors, CFRA);
|
||||
|
|
|
@ -1857,7 +1857,7 @@ static int image_to_gpencil_exec(bContext *C, wmOperator *op)
|
|||
|
||||
/* Add layer and frame. */
|
||||
bGPdata *gpd = (bGPdata *)ob->data;
|
||||
bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, "Image Layer", true);
|
||||
bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, "Image Layer", true, false);
|
||||
bGPDframe *gpf = BKE_gpencil_frame_addnew(gpl, CFRA);
|
||||
done = BKE_gpencil_from_image(sima, gpd, gpf, size, is_mask);
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue