GPv3: Lattice modifier #117955
|
@ -189,6 +189,7 @@ class OBJECT_MT_modifier_add_deform(ModifierAddMenu, Menu):
|
|||
if ob_type == 'VOLUME':
|
||||
self.operator_modifier_add(layout, 'VOLUME_DISPLACE')
|
||||
if ob_type == 'GREASEPENCIL':
|
||||
self.operator_modifier_add(layout, 'GREASE_PENCIL_LATTICE')
|
||||
self.operator_modifier_add(layout, 'GREASE_PENCIL_NOISE')
|
||||
self.operator_modifier_add(layout, 'GREASE_PENCIL_OFFSET')
|
||||
self.operator_modifier_add(layout, 'GREASE_PENCIL_SMOOTH')
|
||||
|
|
|
@ -872,4 +872,10 @@
|
|||
.thickness = 0.02, \
|
||||
}
|
||||
|
||||
#define _DNA_DEFAULT_GreasePencilLatticeModifierData \
|
||||
{ \
|
||||
.object = NULL, \
|
||||
.strength = 1.0f, \
|
||||
}
|
||||
|
||||
/* clang-format off */
|
||||
|
|
|
@ -102,6 +102,7 @@ typedef enum ModifierType {
|
|||
eModifierType_GreasePencilNoise = 67,
|
||||
eModifierType_GreasePencilMirror = 68,
|
||||
eModifierType_GreasePencilThickness = 69,
|
||||
eModifierType_GreasePencilLattice = 70,
|
||||
NUM_MODIFIER_TYPES,
|
||||
} ModifierType;
|
||||
|
||||
|
@ -2772,3 +2773,11 @@ typedef enum GreasePencilThicknessModifierFlag {
|
|||
MOD_GREASE_PENCIL_THICK_NORMALIZE = (1 << 0),
|
||||
MOD_GREASE_PENCIL_THICK_WEIGHT_FACTOR = (1 << 1),
|
||||
} GreasePencilThicknessModifierFlag;
|
||||
|
||||
typedef struct GreasePencilLatticeModifierData {
|
||||
ModifierData modifier;
|
||||
GreasePencilModifierInfluenceData influence;
|
||||
struct Object *object;
|
||||
float strength;
|
||||
char _pad[4];
|
||||
} GreasePencilLatticeModifierData;
|
||||
|
|
|
@ -333,6 +333,7 @@ SDNA_DEFAULT_DECL_STRUCT(GreasePencilTintModifierData);
|
|||
SDNA_DEFAULT_DECL_STRUCT(GreasePencilOffsetModifierData);
|
||||
SDNA_DEFAULT_DECL_STRUCT(GreasePencilMirrorModifierData);
|
||||
SDNA_DEFAULT_DECL_STRUCT(GreasePencilThickModifierData);
|
||||
SDNA_DEFAULT_DECL_STRUCT(GreasePencilLatticeModifierData);
|
||||
|
||||
#undef SDNA_DEFAULT_DECL_STRUCT
|
||||
|
||||
|
@ -588,6 +589,7 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = {
|
|||
SDNA_DEFAULT_DECL(GreasePencilOffsetModifierData),
|
||||
SDNA_DEFAULT_DECL(GreasePencilMirrorModifierData),
|
||||
SDNA_DEFAULT_DECL(GreasePencilThickModifierData),
|
||||
SDNA_DEFAULT_DECL(GreasePencilLatticeModifierData),
|
||||
};
|
||||
#undef SDNA_DEFAULT_DECL
|
||||
#undef SDNA_DEFAULT_DECL_EX
|
||||
|
|
|
@ -321,6 +321,11 @@ const EnumPropertyItem rna_enum_object_modifier_type_items[] = {
|
|||
ICON_MOD_THICKNESS,
|
||||
"Thickness",
|
||||
"Change stroke thickness"},
|
||||
{eModifierType_GreasePencilLattice,
|
||||
"GREASE_PENCIL_LATTICE",
|
||||
ICON_MOD_LATTICE,
|
||||
"Lattice",
|
||||
"Deform strokes using a lattice object"},
|
||||
|
||||
RNA_ENUM_ITEM_HEADING(N_("Physics"), nullptr),
|
||||
{eModifierType_Cloth, "CLOTH", ICON_MOD_CLOTH, "Cloth", ""},
|
||||
|
@ -945,6 +950,7 @@ RNA_MOD_OBJECT_SET(Shrinkwrap, auxTarget, OB_MESH);
|
|||
RNA_MOD_OBJECT_SET(SurfaceDeform, target, OB_MESH);
|
||||
RNA_MOD_OBJECT_SET(GreasePencilMirror, object, OB_EMPTY);
|
||||
RNA_MOD_OBJECT_SET(GreasePencilTint, object, OB_EMPTY);
|
||||
LukasTonne marked this conversation as resolved
|
||||
RNA_MOD_OBJECT_SET(GreasePencilLattice, object, OB_LATTICE);
|
||||
|
||||
static void rna_HookModifier_object_set(PointerRNA *ptr,
|
||||
PointerRNA value,
|
||||
|
@ -1856,6 +1862,7 @@ RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilTint);
|
|||
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilSmooth);
|
||||
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilNoise);
|
||||
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilThick);
|
||||
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilLattice);
|
||||
|
||||
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilOffset);
|
||||
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilOpacity);
|
||||
|
@ -1864,6 +1871,7 @@ RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilTint);
|
|||
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilSmooth);
|
||||
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilNoise);
|
||||
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilThick);
|
||||
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilLattice);
|
||||
|
||||
static void rna_GreasePencilOpacityModifier_opacity_factor_range(
|
||||
PointerRNA *ptr, float *min, float *max, float *softmin, float *softmax)
|
||||
|
@ -8401,6 +8409,46 @@ static void rna_def_modifier_grease_pencil_thickness(BlenderRNA *brna)
|
|||
RNA_define_lib_overridable(false);
|
||||
}
|
||||
|
||||
static void rna_def_modifier_grease_pencil_lattice(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
PropertyRNA *prop;
|
||||
|
||||
srna = RNA_def_struct(brna, "GreasePencilLatticeModifier", "Modifier");
|
||||
RNA_def_struct_ui_text(
|
||||
srna, "Grease Pencil Lattice Modifier", "Deform strokes using a lattice object");
|
||||
RNA_def_struct_sdna(srna, "GreasePencilLatticeModifierData");
|
||||
RNA_def_struct_ui_icon(srna, ICON_MOD_LATTICE);
|
||||
|
||||
rna_def_modifier_grease_pencil_layer_filter(srna);
|
||||
rna_def_modifier_grease_pencil_material_filter(
|
||||
srna, "rna_GreasePencilLatticeModifier_material_filter_set");
|
||||
rna_def_modifier_grease_pencil_vertex_group(
|
||||
srna, "rna_GreasePencilLatticeModifier_vertex_group_name_set");
|
||||
|
||||
rna_def_modifier_panel_open_prop(srna, "open_influence_panel", 0);
|
||||
|
||||
RNA_define_lib_overridable(true);
|
||||
|
||||
prop = RNA_def_property(srna, "object", PROP_POINTER, PROP_NONE);
|
||||
RNA_def_property_ui_text(prop, "Object", "Lattice object to deform with");
|
||||
RNA_def_property_pointer_funcs(prop,
|
||||
nullptr,
|
||||
"rna_GreasePencilLatticeModifier_object_set",
|
||||
nullptr,
|
||||
"rna_Lattice_object_poll");
|
||||
RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK);
|
||||
RNA_def_property_update(prop, 0, "rna_Modifier_dependency_update");
|
||||
|
||||
prop = RNA_def_property(srna, "strength", PROP_FLOAT, PROP_NONE);
|
||||
RNA_def_property_range(prop, -FLT_MAX, FLT_MAX);
|
||||
RNA_def_property_ui_range(prop, 0, 1, 10, 2);
|
||||
RNA_def_property_ui_text(prop, "Strength", "Strength of modifier effect");
|
||||
RNA_def_property_update(prop, 0, "rna_Modifier_update");
|
||||
|
||||
RNA_define_lib_overridable(false);
|
||||
}
|
||||
|
||||
void RNA_def_modifier(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
|
@ -8577,6 +8625,7 @@ void RNA_def_modifier(BlenderRNA *brna)
|
|||
rna_def_modifier_grease_pencil_noise(brna);
|
||||
rna_def_modifier_grease_pencil_mirror(brna);
|
||||
rna_def_modifier_grease_pencil_thickness(brna);
|
||||
rna_def_modifier_grease_pencil_lattice(brna);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -45,6 +45,7 @@ set(SRC
|
|||
intern/MOD_explode.cc
|
||||
intern/MOD_fluid.cc
|
||||
intern/MOD_grease_pencil_color.cc
|
||||
intern/MOD_grease_pencil_lattice.cc
|
||||
intern/MOD_grease_pencil_mirror.cc
|
||||
intern/MOD_grease_pencil_noise.cc
|
||||
intern/MOD_grease_pencil_offset.cc
|
||||
|
|
|
@ -82,6 +82,7 @@ extern ModifierTypeInfo modifierType_GreasePencilOffset;
|
|||
extern ModifierTypeInfo modifierType_GreasePencilNoise;
|
||||
extern ModifierTypeInfo modifierType_GreasePencilMirror;
|
||||
extern ModifierTypeInfo modifierType_GreasePencilThickness;
|
||||
extern ModifierTypeInfo modifierType_GreasePencilLattice;
|
||||
|
||||
/* MOD_util.cc */
|
||||
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup modifiers
|
||||
*/
|
||||
|
||||
#include "DNA_defaults.h"
|
||||
#include "DNA_modifier_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BKE_colortools.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_geometry_set.hh"
|
||||
#include "BKE_grease_pencil.hh"
|
||||
#include "BKE_lattice.hh"
|
||||
#include "BKE_lib_query.hh"
|
||||
#include "BKE_modifier.hh"
|
||||
#include "BKE_screen.hh"
|
||||
|
||||
#include "BLO_read_write.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "BLT_translation.h"
|
||||
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_enum_types.hh"
|
||||
#include "RNA_prototypes.h"
|
||||
|
||||
#include "MOD_grease_pencil_util.hh"
|
||||
#include "MOD_modifiertypes.hh"
|
||||
#include "MOD_ui_common.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
using bke::greasepencil::Drawing;
|
||||
using bke::greasepencil::FramesMapKey;
|
||||
using bke::greasepencil::Layer;
|
||||
|
||||
static void init_data(ModifierData *md)
|
||||
{
|
||||
auto *lmd = reinterpret_cast<GreasePencilLatticeModifierData *>(md);
|
||||
|
||||
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(lmd, modifier));
|
||||
|
||||
MEMCPY_STRUCT_AFTER(lmd, DNA_struct_default_get(GreasePencilLatticeModifierData), modifier);
|
||||
modifier::greasepencil::init_influence_data(&lmd->influence, false);
|
||||
}
|
||||
|
||||
static void copy_data(const ModifierData *md, ModifierData *target, const int flag)
|
||||
{
|
||||
const auto *lmd = reinterpret_cast<const GreasePencilLatticeModifierData *>(md);
|
||||
auto *tlmd = reinterpret_cast<GreasePencilLatticeModifierData *>(target);
|
||||
|
||||
modifier::greasepencil::free_influence_data(&tlmd->influence);
|
||||
|
||||
BKE_modifier_copydata_generic(md, target, flag);
|
||||
modifier::greasepencil::copy_influence_data(&lmd->influence, &tlmd->influence, flag);
|
||||
}
|
||||
|
||||
static void free_data(ModifierData *md)
|
||||
{
|
||||
auto *lmd = reinterpret_cast<GreasePencilLatticeModifierData *>(md);
|
||||
modifier::greasepencil::free_influence_data(&lmd->influence);
|
||||
}
|
||||
|
||||
static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data)
|
||||
{
|
||||
auto *lmd = reinterpret_cast<GreasePencilLatticeModifierData *>(md);
|
||||
modifier::greasepencil::foreach_influence_ID_link(&lmd->influence, ob, walk, user_data);
|
||||
|
||||
walk(user_data, ob, (ID **)&lmd->object, IDWALK_CB_NOP);
|
||||
}
|
||||
|
||||
static void update_depsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx)
|
||||
{
|
||||
auto *lmd = reinterpret_cast<GreasePencilLatticeModifierData *>(md);
|
||||
if (lmd->object != nullptr) {
|
||||
DEG_add_object_relation(
|
||||
ctx->node, lmd->object, DEG_OB_COMP_TRANSFORM, "Grease Pencil Lattice Modifier");
|
||||
DEG_add_object_relation(
|
||||
ctx->node, lmd->object, DEG_OB_COMP_GEOMETRY, "Grease Pencil Lattice Modifier");
|
||||
}
|
||||
DEG_add_depends_on_transform_relation(ctx->node, "Grease Pencil Lattice Modifier");
|
||||
}
|
||||
|
||||
static bool is_disabled(const Scene * /*scene*/, ModifierData *md, bool /*use_render_params*/)
|
||||
{
|
||||
auto *lmd = reinterpret_cast<GreasePencilLatticeModifierData *>(md);
|
||||
|
||||
/* The object type check is only needed here in case we have a placeholder
|
||||
* object assigned (because the library containing the lattice is missing).
|
||||
*
|
||||
* In other cases it should be impossible to have a type mismatch.
|
||||
*/
|
||||
return lmd->object == nullptr || lmd->object->type != OB_LATTICE;
|
||||
}
|
||||
|
||||
static void modify_curves(ModifierData *md,
|
||||
const ModifierEvalContext *ctx,
|
||||
const LatticeDeformData &cache_data,
|
||||
Drawing &drawing)
|
||||
{
|
||||
const auto *lmd = reinterpret_cast<GreasePencilLatticeModifierData *>(md);
|
||||
bke::CurvesGeometry &curves = drawing.strokes_for_write();
|
||||
|
||||
IndexMaskMemory mask_memory;
|
||||
const IndexMask curves_mask = modifier::greasepencil::get_filtered_stroke_mask(
|
||||
ctx->object, curves, lmd->influence, mask_memory);
|
||||
|
||||
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
|
||||
MutableSpan<float3> positions = curves.positions_for_write();
|
||||
const VArray<float> vgroup_weights = modifier::greasepencil::get_influence_vertex_weights(
|
||||
curves, lmd->influence);
|
||||
|
||||
curves_mask.foreach_index(GrainSize(512), [&](const int64_t curve_i) {
|
||||
const IndexRange points = points_by_curve[curve_i];
|
||||
for (const int64_t point_i : points) {
|
||||
const float weight = vgroup_weights[point_i];
|
||||
BKE_lattice_deform_data_eval_co(const_cast<LatticeDeformData *>(&cache_data),
|
||||
positions[point_i],
|
||||
lmd->strength * weight);
|
||||
}
|
||||
});
|
||||
|
||||
LukasTonne marked this conversation as resolved
Hans Goudey
commented
Looks like the drawing needs a positions changed tag too Looks like the drawing needs a positions changed tag too
|
||||
drawing.tag_positions_changed();
|
||||
LukasTonne marked this conversation as resolved
Outdated
Falk David
commented
Tagging the drawing will tag the curves geometry, so this line can be removed now. Tagging the drawing will tag the curves geometry, so this line can be removed now.
|
||||
}
|
||||
|
||||
static void modify_geometry_set(ModifierData *md,
|
||||
const ModifierEvalContext *ctx,
|
||||
bke::GeometrySet *geometry_set)
|
||||
{
|
||||
const auto *lmd = reinterpret_cast<GreasePencilLatticeModifierData *>(md);
|
||||
BLI_assert(lmd->object != nullptr && lmd->object->type == OB_LATTICE);
|
||||
LatticeDeformData *cache_data = BKE_lattice_deform_data_create(lmd->object, ctx->object);
|
||||
|
||||
if (!geometry_set->has_grease_pencil()) {
|
||||
return;
|
||||
LukasTonne marked this conversation as resolved
Falk David
commented
We've been using this pattern in other modifiers, which is a bit nicer imho:
We've been using this pattern in other modifiers, which is a bit nicer imho:
```
if (!geometry_set->has_grease_pencil()) {
return;
}
GreasePencil &grease_pencil = *geometry_set->get_grease_pencil_for_write();
```
|
||||
}
|
||||
GreasePencil &grease_pencil = *geometry_set->get_grease_pencil_for_write();
|
||||
|
||||
IndexMaskMemory mask_memory;
|
||||
const IndexMask layer_mask = modifier::greasepencil::get_filtered_layer_mask(
|
||||
LukasTonne marked this conversation as resolved
Falk David
commented
I haven't thought about this before, but I think we should call
in modifiers that deform the drawings. Maybe this should be done as a seperate PR though. I haven't thought about this before, but I think we should call
```
GeometryComponentEditData::remember_deformed_positions_if_necessary(geometry_set);
```
in modifiers that deform the drawings.
Maybe this should be done as a seperate PR though.
Lukas Tönne
commented
Added a note to #106665, will ignore it here for now. Added a note to #106665, will ignore it here for now.
|
||||
grease_pencil, lmd->influence, mask_memory);
|
||||
const int frame = grease_pencil.runtime->eval_frame;
|
||||
const Vector<Drawing *> drawings = modifier::greasepencil::get_drawings_for_write(
|
||||
grease_pencil, layer_mask, frame);
|
||||
threading::parallel_for_each(
|
||||
drawings, [&](Drawing *drawing) { modify_curves(md, ctx, *cache_data, *drawing); });
|
||||
|
||||
BKE_lattice_deform_data_destroy(cache_data);
|
||||
}
|
||||
|
||||
static void panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
uiLayout *layout = panel->layout;
|
||||
|
||||
PointerRNA ob_ptr;
|
||||
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
|
||||
|
||||
uiLayoutSetPropSep(layout, true);
|
||||
|
||||
uiItemR(layout, ptr, "object", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(layout, ptr, "strength", UI_ITEM_R_SLIDER, nullptr, ICON_NONE);
|
||||
|
||||
if (uiLayout *influence_panel = uiLayoutPanelProp(
|
||||
C, layout, ptr, "open_influence_panel", "Influence"))
|
||||
{
|
||||
modifier::greasepencil::draw_layer_filter_settings(C, influence_panel, ptr);
|
||||
modifier::greasepencil::draw_material_filter_settings(C, influence_panel, ptr);
|
||||
modifier::greasepencil::draw_vertex_group_settings(C, influence_panel, ptr);
|
||||
}
|
||||
|
||||
modifier_panel_end(layout, ptr);
|
||||
}
|
||||
|
||||
static void panel_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eModifierType_GreasePencilLattice, panel_draw);
|
||||
}
|
||||
|
||||
static void blend_write(BlendWriter *writer, const ID * /*id_owner*/, const ModifierData *md)
|
||||
{
|
||||
const auto *lmd = reinterpret_cast<const GreasePencilLatticeModifierData *>(md);
|
||||
|
||||
BLO_write_struct(writer, GreasePencilLatticeModifierData, lmd);
|
||||
modifier::greasepencil::write_influence_data(writer, &lmd->influence);
|
||||
}
|
||||
|
||||
static void blend_read(BlendDataReader *reader, ModifierData *md)
|
||||
{
|
||||
auto *lmd = reinterpret_cast<GreasePencilLatticeModifierData *>(md);
|
||||
|
||||
modifier::greasepencil::read_influence_data(reader, &lmd->influence);
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
|
||||
ModifierTypeInfo modifierType_GreasePencilLattice = {
|
||||
/*idname*/ "GreasePencilLattice",
|
||||
/*name*/ N_("Lattice"),
|
||||
/*struct_name*/ "GreasePencilLatticeModifierData",
|
||||
/*struct_size*/ sizeof(GreasePencilLatticeModifierData),
|
||||
/*srna*/ &RNA_GreasePencilLatticeModifier,
|
||||
/*type*/ ModifierTypeType::OnlyDeform,
|
||||
/*flags*/ eModifierTypeFlag_AcceptsGreasePencil | eModifierTypeFlag_SupportsEditmode |
|
||||
eModifierTypeFlag_EnableInEditmode | eModifierTypeFlag_SupportsMapping,
|
||||
/*icon*/ ICON_MOD_LATTICE,
|
||||
|
||||
/*copy_data*/ blender::copy_data,
|
||||
|
||||
/*deform_verts*/ nullptr,
|
||||
/*deform_matrices*/ nullptr,
|
||||
/*deform_verts_EM*/ nullptr,
|
||||
/*deform_matrices_EM*/ nullptr,
|
||||
/*modify_mesh*/ nullptr,
|
||||
/*modify_geometry_set*/ blender::modify_geometry_set,
|
||||
|
||||
/*init_data*/ blender::init_data,
|
||||
/*required_data_mask*/ nullptr,
|
||||
/*free_data*/ blender::free_data,
|
||||
/*is_disabled*/ blender::is_disabled,
|
||||
/*update_depsgraph*/ blender::update_depsgraph,
|
||||
/*depends_on_time*/ nullptr,
|
||||
/*depends_on_normals*/ nullptr,
|
||||
/*foreach_ID_link*/ blender::foreach_ID_link,
|
||||
/*foreach_tex_link*/ nullptr,
|
||||
/*free_runtime_data*/ nullptr,
|
||||
/*panel_register*/ blender::panel_register,
|
||||
/*blend_write*/ blender::blend_write,
|
||||
/*blend_read*/ blender::blend_read,
|
||||
/*foreach_cache*/ nullptr,
|
||||
};
|
|
@ -279,5 +279,6 @@ void modifier_type_init(ModifierTypeInfo *types[])
|
|||
INIT_TYPE(GreasePencilNoise);
|
||||
INIT_TYPE(GreasePencilMirror);
|
||||
INIT_TYPE(GreasePencilThickness);
|
||||
INIT_TYPE(GreasePencilLattice);
|
||||
#undef INIT_TYPE
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Could you commit these changes seperately as a fix? Same for removing the rna setter functions below.