#include "pch.h" #include "log.h" #include "brush.h" #include "asset.h" #include "app.h" StrokeSample Stroke::randomize_sample(const glm::vec3& pos, float pressure, float dir_angle) { float size_dyn = m_brush->m_tip_size_pressure ? pressure : 1.f; float flow_dyn = m_brush->m_tip_flow_pressure ? pressure : 1.f; float opacity_dyn = m_brush->m_tip_opacity_pressure ? pressure : 1.f; float size = glm::min(m_brush->m_tip_size / glm::tan(glm::radians(m_camera.fov * 0.5f)), m_max_size); float randflipx = m_brush->m_tip_randflipx ? rnd_bneg() : 1.f; float randflipy = m_brush->m_tip_randflipy ? rnd_bneg() : 1.f; glm::vec2 scatter_axis = m_brush->m_jitter_scatter_bothaxis ? glm::vec2(1.f, 1.f) : glm::vec2(0.f, 1.f); auto scatter_scale = glm::vec3(scatter_axis * glm::orientate2(-dir_angle), 1.f); float aspect_jitter = m_brush->m_jitter_aspect_bothaxis ? rnd_neg() * 0.5f * m_brush->m_jitter_aspect : rnd_nor() * 0.5f * m_brush->m_jitter_aspect; float aspect = glm::clamp(m_brush->m_tip_aspect + aspect_jitter, 0.1f, 0.9f); glm::vec2 aspect_scale = { (aspect <= 0.5 ? aspect * 2.f : 1.f), (aspect > 0.5 ? 1.f - (aspect - .5f) * 2.f : 1.f) }; StrokeSample s; s.origin = pos; s.scale = m_brush->m_tip_scale * randflipx * (m_brush->m_tip_flipx ? -1.f : 1.f) * aspect_scale; s.angle = (m_brush->m_tip_angle + rnd_neg() * m_brush->m_jitter_angle) * (float)(M_PI * 2.0); s.size = size * (1.f - rnd_nor() * m_brush->m_jitter_scale) * size_dyn; s.pos = pos + (scatter_scale * rnd_vec() * m_brush->m_jitter_scatter * s.size * 0.5f); // 0.5 because PS scatters by half size s.flow = m_brush->m_tip_flow * (1.f - rnd_nor() * m_brush->m_jitter_flow) * flow_dyn; s.opacity = m_brush->m_tip_opacity * (1.f - rnd_nor() * m_brush->m_jitter_opacity) * opacity_dyn; auto hsv = m_tip_color; float eachtip = m_brush->m_jitter_hsv_eachsample ? 1.f : 0.f; hsv.x = glm::fract(glm::mix(hsv.x, (pressure - 0.5f) * 2.0f, m_brush->m_tip_hue) + (rnd_nor() - 0.5f) * m_brush->m_jitter_hue * eachtip); hsv.y = glm::clamp(glm::mix(hsv.y, (1.f - pressure - 0.5f) * 2.0f, m_brush->m_tip_sat) + (rnd_nor() - 0.5f) * m_brush->m_jitter_sat * eachtip, 0.f, 1.f); hsv.z = glm::clamp(glm::mix(hsv.z, (pressure - 0.5f) * 2.0f, m_brush->m_tip_val) + (rnd_nor() - 0.5f) * m_brush->m_jitter_val * eachtip, 0.f, 1.f); //m_hsv_jitter.add(hsv); s.col = convert_hsv2rgb(hsv); return s; } void Stroke::randomize_prng() { std::random_device rd; prng.seed(rd()); } std::vector Stroke::compute_samples() { if (m_keypoints.size() < 2) return {}; int nsamples = (int)glm::floor((m_keypoints.back().dist - m_dist) / m_step); std::vector samples; samples.reserve(nsamples); // preallocate the estimate number of samples while (m_keypoints.back().dist > (m_dist + m_step)) { m_dist += m_step; m_dir_dist += m_step; int old_kp = m_last_kp; while (m_dist > m_keypoints[m_last_kp + 1].dist && m_last_kp < m_keypoints.size()) m_last_kp++; const auto& A = m_keypoints[old_kp]; const auto& B = m_keypoints[m_last_kp == old_kp ? m_last_kp + 1 : m_last_kp]; // NOTE: this should be true when while is true float t = (m_dist - A.dist) / (B.dist - A.dist); // NOTE: must be A != B auto pos = glm::lerp(A.pos, B.pos, t); float pressure = glm::lerp(A.pressure, B.pressure, t); if (m_dir_dist > m_dir_step) { int next_kp = m_last_kp == m_dir_kp ? m_last_kp + 1 : m_dir_kp; glm::vec2 v = glm::normalize(m_keypoints[m_last_kp].pos - m_keypoints[next_kp].pos); m_dir_angle = -glm::orientedAngle(v, m_dir_ref); if (m_brush->m_tip_angle_smooth > 0 && (glm::abs(m_dir_angle) > glm::radians(30.f) || !m_dir_valid)) { if (glm::abs(m_dir_angle) > glm::radians(100.f)) { //LOG("BIG ANGLE"); m_direction.clear(); } auto old_dir = m_dir_ref; m_dir_ref = v; m_dir_ref_angle = -glm::orientedAngle(m_dir_ref, { 1, 0 }); m_dir_angle = 0; auto angle_diff = -glm::orientedAngle(m_dir_ref, old_dir); for (int i = 0; i < m_direction.m_count; i++) m_direction.m_vec[i] -= angle_diff; } m_dir_kp = m_last_kp; m_dir_dist = 0; if (!m_dir_valid) { m_dir_init = m_dir_angle + m_dir_ref_angle; m_dir_valid = true; } } bool need_dir = false; need_dir |= m_brush->m_tip_angle_follow; need_dir |= m_brush->m_tip_angle_init; need_dir |= m_brush->m_jitter_angle > 0; // angle is not ready yet if (need_dir && !m_dir_valid) continue; auto s = randomize_sample(pos, pressure, m_dir_angle + m_dir_ref_angle); if (s.valid()) { if (m_brush->m_tip_angle_follow) { m_direction.add(m_dir_angle); s.angle += m_direction.average() + m_dir_ref_angle; } else if (m_brush->m_tip_angle_init) { s.angle += m_dir_init; } m_prev_sample = s; samples.push_back(s); } else { static bool invalid_logged = false; if (!invalid_logged) { for (auto const& p : m_keypoints) LOG("point dist %f pos %f %f %f", p.dist, p.pos.x, p.pos.y, p.pos.z); invalid_logged = true; } LOG("A.dist %f B.dist %f", A.dist, B.dist); LOG("Invalid sample"); } } return samples; } bool Stroke::has_sample() { return m_keypoints.size() < 2 ? false : // no keypoints (m_keypoints.back().dist > (m_dist + m_step)); // check if next kp is closer than spacing } void Stroke::reset(bool clear_keypoints /*= false*/) { m_dir_kp = 0; m_dir_angle = 0; m_dir_valid = false; m_dir_dist = 0; m_last_kp = 0; m_dist = 0.f; if (clear_keypoints) m_keypoints.clear(); } void Stroke::add_point(glm::vec3 pos, float pressure) { #ifdef __IOS__ m_curve = glm::min(m_curve + 0.1f, 1.f); //pressure = pressure * glm::pow(m_curve, 2.f); if (m_filter_points && m_hold_points.size() < 5) { m_hold_points.push_back({pos, pressure}); return; } #endif // __IOS__ //m_pressure_buff.add(pressure); //pressure = m_pressure_buff.average(); if (m_brush->m_tip_size_pressure) { float aspect_width = glm::min(1.f, glm::clamp(m_brush->m_tip_aspect, .1f, .9f) * 2.f); float raw_size = glm::clamp(m_brush->m_tip_size / glm::tan(glm::radians(m_camera.fov * 0.5f)), 1.f, m_max_size); float size = aspect_width * glm::min(m_brush->m_tip_scale.x, m_brush->m_tip_scale.y) * raw_size; m_step = glm::max(0.5f, m_brush->m_tip_spacing * size * App::I->zoom * pressure); } float dist = m_keypoints.empty() ? m_step : m_keypoints.back().dist + glm::distance(m_keypoints.back().pos, pos); if (m_keypoints.empty()) m_prev_sample = randomize_sample(pos, pressure, 0); else if (m_keypoints.back().pos == pos) return; // skip same point, leading to black samples (NaN values) Keypoint kp; kp.pos = pos; kp.pressure = pressure; kp.dist = dist; m_keypoints.push_back(kp); } void Stroke::start(const std::shared_ptr& brush) { m_hold_points.clear(); m_curve = 0.f; m_direction.clear(); m_pressure_buff.clear(); m_hsv_jitter.clear(); m_last_kp = 0; m_dist = 0.f; m_dir_kp = 0; m_dir_angle = 0; m_dir_valid = false; m_dir_dist = 0; m_brush = brush; float aspect_width = glm::min(1.f, glm::clamp(m_brush->m_tip_aspect, .1f, .9f) * 2.f); float raw_size = glm::clamp(m_brush->m_tip_size / glm::tan(glm::radians(m_camera.fov * 0.5f)), 1.f, m_max_size); float size = aspect_width * glm::min(m_brush->m_tip_scale.x, m_brush->m_tip_scale.y) * raw_size; m_step = glm::max(0.5f, m_brush->m_tip_spacing * size * App::I->zoom); auto hsv = convert_rgb2hsv(m_brush->m_tip_color); if (!m_brush->m_jitter_hsv_eachsample) { hsv.x = glm::fract(hsv.x + (rnd_nor() - 0.5f) * m_brush->m_jitter_hue); hsv.y = glm::clamp(hsv.y + (rnd_nor() - 0.5f) * m_brush->m_jitter_sat, 0.f, 1.f); hsv.z = glm::clamp(hsv.z + (rnd_nor() - 0.5f) * m_brush->m_jitter_val, 0.f, 1.f); } m_tip_color = hsv; m_direction.resize(std::max(1, m_brush->m_tip_angle_smooth * 200.f / m_step)); prng.seed(0); } bool Brush::load_tip(const std::string& path, const std::string& thumb) { if (m_tip_texture && m_brush_path == path) return true; m_tip_texture = std::make_shared(); if (!m_tip_texture->load(path)) { m_tip_texture = nullptr; return false; } m_tip_texture->create_mipmaps(); m_tip_texture->auto_destroy = true; m_brush_path = path; m_brush_thumb_path = thumb; auto sz = m_tip_texture->size(); m_tip_scale = sz.y > sz.x ? glm::vec2(sz.x / sz.y, 1.f) : glm::vec2(1.f, sz.y / sz.x); return true; } bool Brush::load_dual(const std::string& path, const std::string& thumb) { if (m_dual_texture && m_dual_path == path) return true; m_dual_texture = std::make_shared(); if (!m_dual_texture->load(path)) { m_dual_texture = nullptr; return false; } m_dual_texture->create_mipmaps(); m_dual_texture->auto_destroy = true; m_dual_path = path; m_dual_thumb_path = thumb; return true; } bool Brush::load_pattern(const std::string& path, const std::string& thumb) { if (m_pattern_texture && m_pattern_path == path) return true; m_pattern_texture = std::make_shared(); if (!m_pattern_texture->load(path)) { m_pattern_texture = nullptr; return false; } m_pattern_texture->create_mipmaps(); m_pattern_texture->auto_destroy = true; m_pattern_path = path; m_pattern_thumb_path = thumb; return true; } bool Brush::load() { if (!m_brush_path.empty() && !m_tip_texture) { m_tip_texture = std::make_shared(); auto loaded = m_tip_img ? m_tip_texture->create(*m_tip_img) : m_tip_texture->load(m_brush_path); if (!loaded) { LOG("failed to load %s", m_brush_path.c_str()); m_tip_texture = nullptr; return false; } m_tip_texture->create_mipmaps(); m_tip_texture->auto_destroy = true; } if (!m_dual_path.empty() && !m_dual_texture) { m_dual_texture = std::make_shared(); auto loaded = m_dual_img ? m_dual_texture->create(*m_dual_img) : m_dual_texture->load(m_dual_path); if (!loaded) { LOG("failed to load %s", m_dual_path.c_str()); m_tip_texture = nullptr; m_dual_texture = nullptr; return false; } m_dual_texture->create_mipmaps(); m_dual_texture->auto_destroy = true; } if (!m_pattern_path.empty() && !m_pattern_texture) { m_pattern_texture = std::make_shared(); auto loaded = m_pattern_img ? m_pattern_texture->create(*m_pattern_img) : m_pattern_texture->load(m_pattern_path); if (!loaded) { LOG("failed to load %s", m_pattern_path.c_str()); m_tip_texture = nullptr; m_dual_texture = nullptr; m_pattern_texture = nullptr; return false; } m_pattern_texture->create_mipmaps(); m_pattern_texture->auto_destroy = true; } return true; } bool Brush::preload() { if (!m_brush_path.empty() && !m_tip_texture && !m_tip_img) { m_tip_img = std::make_shared(); if (!m_tip_img->load(m_brush_path)) return false; } if (!m_dual_path.empty() && !m_dual_texture && !m_dual_img) { m_dual_img = std::make_shared(); if (!m_dual_img->load(m_dual_path)) return false; } if (!m_pattern_path.empty() && !m_pattern_texture && !m_pattern_img) { m_pattern_img = std::make_shared(); if (!m_pattern_img->load(m_pattern_path)) return false; } return true; } void Brush::unload() { m_tip_texture = nullptr; m_tip_img = nullptr; m_dual_texture = nullptr; m_dual_img = nullptr; m_pattern_texture = nullptr; m_pattern_img = nullptr; } bool Brush::valid() { if (!m_brush_path.empty() && !Asset::exist(m_brush_path)) return false; if (!m_dual_path.empty() && !Asset::exist(m_dual_path)) return false; if (!m_pattern_path.empty() && !Asset::exist(m_pattern_path)) return false; return true; } void Brush::relocate_paths(std::string base) { if (!Asset::is_asset(m_brush_path)) m_brush_path = replace_path(m_brush_path, base + "/brushes/"); if (!Asset::is_asset(m_dual_path)) m_dual_path = replace_path(m_dual_path, base + "/brushes/"); if (!Asset::is_asset(m_pattern_path)) m_pattern_path = replace_path(m_pattern_path, base + "/patterns/"); } std::string Brush::replace_path(std::string path, std::string new_base) { if (path.empty()) return path; std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)"); std::smatch m; if (!std::regex_search(path, m, r)) return path; std::string base = m[1].str(); std::string name = m[2].str(); std::string ext = m[3].str(); return new_base + "/" + name + "." + ext; } bool Brush::read(BinaryStreamReader& r) { Serializer::Descriptor d; r >> d; d.value("m_name", m_name); d.value("m_brush_path", m_brush_path); m_brush_path = str_replace(m_brush_path, "{data_path}", App::I->data_path); d.value("m_brush_thumb_path", m_brush_thumb_path); m_brush_thumb_path = str_replace(m_brush_thumb_path, "{data_path}", App::I->data_path); d.value("m_dual_path", m_dual_path); m_dual_path = str_replace(m_dual_path, "{data_path}", App::I->data_path); d.value("m_dual_thumb_path", m_dual_thumb_path); m_dual_thumb_path = str_replace(m_dual_thumb_path, "{data_path}", App::I->data_path); d.value("m_pattern_path", m_pattern_path); m_pattern_path = str_replace(m_pattern_path, "{data_path}", App::I->data_path); d.value("m_pattern_thumb_path", m_pattern_thumb_path); m_pattern_thumb_path = str_replace(m_pattern_thumb_path, "{data_path}", App::I->data_path); d.value("m_tip_color", m_tip_color); d.value("m_tip_scale", m_tip_scale); d.value("m_dual_scale", m_dual_scale); d.value("m_tip_size", m_tip_size); d.value("m_tip_spacing", m_tip_spacing); d.value("m_tip_flow", m_tip_flow); d.value("m_tip_opacity", m_tip_opacity); d.value("m_tip_angle", m_tip_angle); d.value("m_tip_angle_smooth", m_tip_angle_smooth); d.value("m_tip_mix", m_tip_mix); d.value("m_tip_wet", m_tip_wet); d.value("m_tip_noise", m_tip_noise); d.value("m_tip_hue", m_tip_hue); d.value("m_tip_sat", m_tip_sat); d.value("m_tip_val", m_tip_val); d.value("m_jitter_scale", m_jitter_scale); d.value("m_jitter_angle", m_jitter_angle); d.value("m_jitter_scatter", m_jitter_scatter); d.value("m_jitter_flow", m_jitter_flow); d.value("m_jitter_opacity", m_jitter_opacity); d.value("m_jitter_hue", m_jitter_hue); d.value("m_jitter_sat", m_jitter_sat); d.value("m_jitter_val", m_jitter_val); d.value("m_jitter_aspect", m_jitter_aspect); d.value("m_dual_size", m_dual_size); d.value("m_dual_spacing", m_dual_spacing); d.value("m_dual_scatter", m_dual_scatter); d.value("m_tip_aspect", m_tip_aspect); d.value("m_dual_flow", m_dual_flow); d.value("m_dual_opacity", m_dual_opacity); d.value("m_dual_rotate", m_dual_rotate); d.value("m_dual_angle", m_dual_angle); d.value("m_dual_aspect", m_dual_aspect); d.value("m_pattern_scale", m_pattern_scale); d.value("m_pattern_brightness", m_pattern_brightness); d.value("m_pattern_contrast", m_pattern_contrast); d.value("m_pattern_depth", m_pattern_depth); d.value("m_tip_angle_init", m_tip_angle_init); d.value("m_tip_angle_follow", m_tip_angle_follow); d.value("m_tip_flow_pressure", m_tip_flow_pressure); d.value("m_tip_opacity_pressure", m_tip_opacity_pressure); d.value("m_tip_size_pressure", m_tip_size_pressure); d.value("m_jitter_scatter_bothaxis", m_jitter_scatter_bothaxis); d.value("m_jitter_hsv_eachsample", m_jitter_hsv_eachsample); d.value("m_jitter_aspect_bothaxis", m_jitter_aspect_bothaxis); d.value("m_tip_invert", m_tip_invert); d.value("m_tip_flipx", m_tip_flipx); d.value("m_tip_flipy", m_tip_flipy); d.value("m_pattern_enabled", m_pattern_enabled); d.value("m_dual_enabled", m_dual_enabled); d.value("m_dual_randflip", m_dual_randflip); d.value("m_dual_scatter_bothaxis", m_dual_scatter_bothaxis); d.value("m_dual_invert", m_dual_invert); d.value("m_dual_flipx", m_dual_flipx); d.value("m_dual_flipy", m_dual_flipy); d.value("m_tip_randflipx", m_tip_randflipx); d.value("m_tip_randflipy", m_tip_randflipy); d.value("m_pattern_eachsample", m_pattern_eachsample); d.value("m_pattern_invert", m_pattern_invert); d.value("m_pattern_flipx", m_pattern_flipx); d.value("m_pattern_flipy", m_pattern_flipy); d.value("m_pattern_rand_offset", m_pattern_rand_offset); d.value("m_blend_mode", m_blend_mode); d.value("m_dual_blend_mode", m_dual_blend_mode); d.value("m_dual_count", m_dual_count); d.value("m_pattern_blend_mode", m_pattern_blend_mode); return true; } void Brush::write(BinaryStreamWriter& w) const { Serializer::Descriptor d; d.class_id = "brush"; d.name = L"Brush class"; d.props["m_name"] = std::make_shared(m_name); d.props["m_brush_path"] = std::make_shared( str_replace(m_brush_path, App::I->data_path, "{data_path}")); d.props["m_brush_thumb_path"] = std::make_shared( str_replace(m_brush_thumb_path, App::I->data_path, "{data_path}")); d.props["m_dual_path"] = std::make_shared( str_replace(m_dual_path, App::I->data_path, "{data_path}")); d.props["m_dual_thumb_path"] = std::make_shared( str_replace(m_dual_thumb_path, App::I->data_path, "{data_path}")); d.props["m_pattern_path"] = std::make_shared( str_replace(m_pattern_path, App::I->data_path, "{data_path}")); d.props["m_pattern_thumb_path"] = std::make_shared( str_replace(m_pattern_thumb_path, App::I->data_path, "{data_path}")); d.props["m_tip_color"] = std::make_shared(m_tip_color); d.props["m_tip_scale"] = std::make_shared(m_tip_scale); d.props["m_dual_scale"] = std::make_shared(m_dual_scale); d.props["m_tip_size"] = std::make_shared(m_tip_size); d.props["m_tip_spacing"] = std::make_shared(m_tip_spacing); d.props["m_tip_flow"] = std::make_shared(m_tip_flow); d.props["m_tip_opacity"] = std::make_shared(m_tip_opacity); d.props["m_tip_angle"] = std::make_shared(m_tip_angle); d.props["m_tip_angle_smooth"] = std::make_shared(m_tip_angle_smooth); d.props["m_tip_mix"] = std::make_shared(m_tip_mix); d.props["m_tip_wet"] = std::make_shared(m_tip_wet); d.props["m_tip_noise"] = std::make_shared(m_tip_noise); d.props["m_tip_hue"] = std::make_shared(m_tip_hue); d.props["m_tip_sat"] = std::make_shared(m_tip_sat); d.props["m_tip_val"] = std::make_shared(m_tip_val); d.props["m_jitter_scale"] = std::make_shared(m_jitter_scale); d.props["m_jitter_angle"] = std::make_shared(m_jitter_angle); d.props["m_jitter_scatter"] = std::make_shared(m_jitter_scatter); d.props["m_jitter_flow"] = std::make_shared(m_jitter_flow); d.props["m_jitter_opacity"] = std::make_shared(m_jitter_opacity); d.props["m_jitter_hue"] = std::make_shared(m_jitter_hue); d.props["m_jitter_sat"] = std::make_shared(m_jitter_sat); d.props["m_jitter_val"] = std::make_shared(m_jitter_val); d.props["m_jitter_aspect"] = std::make_shared(m_jitter_aspect); d.props["m_dual_size"] = std::make_shared(m_dual_size); d.props["m_dual_spacing"] = std::make_shared(m_dual_spacing); d.props["m_dual_scatter"] = std::make_shared(m_dual_scatter); d.props["m_tip_aspect"] = std::make_shared(m_tip_aspect); d.props["m_dual_flow"] = std::make_shared(m_dual_flow); d.props["m_dual_opacity"] = std::make_shared(m_dual_opacity); d.props["m_dual_rotate"] = std::make_shared(m_dual_rotate); d.props["m_dual_angle"] = std::make_shared(m_dual_angle); d.props["m_dual_aspect"] = std::make_shared(m_dual_aspect); d.props["m_pattern_scale"] = std::make_shared(m_pattern_scale); d.props["m_pattern_brightness"] = std::make_shared(m_pattern_brightness); d.props["m_pattern_contrast"] = std::make_shared(m_pattern_contrast); d.props["m_pattern_depth"] = std::make_shared(m_pattern_depth); d.props["m_tip_angle_init"] = std::make_shared(m_tip_angle_init); d.props["m_tip_angle_follow"] = std::make_shared(m_tip_angle_follow); d.props["m_tip_flow_pressure"] = std::make_shared(m_tip_flow_pressure); d.props["m_tip_opacity_pressure"] = std::make_shared(m_tip_opacity_pressure); d.props["m_tip_size_pressure"] = std::make_shared(m_tip_size_pressure); d.props["m_jitter_scatter_bothaxis"] = std::make_shared(m_jitter_scatter_bothaxis); d.props["m_jitter_hsv_eachsample"] = std::make_shared(m_jitter_hsv_eachsample); d.props["m_jitter_aspect_bothaxis"] = std::make_shared(m_jitter_aspect_bothaxis); d.props["m_tip_invert"] = std::make_shared(m_tip_invert); d.props["m_tip_flipx"] = std::make_shared(m_tip_flipx); d.props["m_tip_flipy"] = std::make_shared(m_tip_flipy); d.props["m_pattern_enabled"] = std::make_shared(m_pattern_enabled); d.props["m_dual_enabled"] = std::make_shared(m_dual_enabled); d.props["m_dual_randflip"] = std::make_shared(m_dual_randflip); d.props["m_dual_scatter_bothaxis"] = std::make_shared(m_dual_scatter_bothaxis); d.props["m_dual_invert"] = std::make_shared(m_dual_invert); d.props["m_dual_flipx"] = std::make_shared(m_dual_flipx); d.props["m_dual_flipy"] = std::make_shared(m_dual_flipy); d.props["m_tip_randflipx"] = std::make_shared(m_tip_randflipx); d.props["m_tip_randflipy"] = std::make_shared(m_tip_randflipy); d.props["m_pattern_eachsample"] = std::make_shared(m_pattern_eachsample); d.props["m_pattern_invert"] = std::make_shared(m_pattern_invert); d.props["m_pattern_flipx"] = std::make_shared(m_pattern_flipx); d.props["m_pattern_flipy"] = std::make_shared(m_pattern_flipy); d.props["m_pattern_rand_offset"] = std::make_shared(m_pattern_rand_offset); d.props["m_blend_mode"] = std::make_shared(m_blend_mode); d.props["m_dual_blend_mode"] = std::make_shared(m_dual_blend_mode); d.props["m_dual_count"] = std::make_shared(m_dual_count); d.props["m_pattern_blend_mode"] = std::make_shared(m_pattern_blend_mode); w << d; }