Added Curve resource

- New resource for curves in y(x) form
- CurveTexture now has a Curve
- Curve and CurveTexture share the same editor
This commit is contained in:
Marc Gilleron 2017-04-30 16:27:10 +02:00
parent 00e5ba3143
commit 659897cfb8
10 changed files with 1260 additions and 734 deletions

View File

@ -6114,7 +6114,7 @@ EditorNode::EditorNode() {
add_editor_plugin(memnew(GradientEditorPlugin(this))); add_editor_plugin(memnew(GradientEditorPlugin(this)));
add_editor_plugin(memnew(GradientTextureEditorPlugin(this))); add_editor_plugin(memnew(GradientTextureEditorPlugin(this)));
add_editor_plugin(memnew(CollisionShape2DEditorPlugin(this))); add_editor_plugin(memnew(CollisionShape2DEditorPlugin(this)));
add_editor_plugin(memnew(CurveTextureEditorPlugin(this))); add_editor_plugin(memnew(CurveEditorPlugin(this)));
add_editor_plugin(memnew(TextureEditorPlugin(this))); add_editor_plugin(memnew(TextureEditorPlugin(this)));
add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor))); add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor)));
//add_editor_plugin( memnew( MaterialEditorPlugin(this) ) ); //add_editor_plugin( memnew( MaterialEditorPlugin(this) ) );

File diff suppressed because it is too large Load Diff

View File

@ -27,69 +27,119 @@
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/ /*************************************************************************/
#ifndef CURVE_EDITOR_PLUGIN_H #ifndef CURVE_EDITOR_PLUGIN_H
#define CURVE_EDITOR_PLUGIN_H #define CURVE_EDITOR_PLUGIN_H
#include "editor/editor_node.h" #include "editor/editor_node.h"
#include "editor/editor_plugin.h" #include "editor/editor_plugin.h"
#include "scene/resources/curve.h"
class CurveTextureEdit : public Control { // Edits a y(x) curve
class CurveEditor : public Control {
GDCLASS(CurveEditor, Control)
public:
CurveEditor();
GDCLASS(CurveTextureEdit, Control); Size2 get_minimum_size() const;
struct Point { void set_curve(Ref<Curve> curve);
float offset; enum PresetID {
float height; PRESET_FLAT0 = 0,
bool operator<(const Point &p_ponit) const { PRESET_FLAT1,
return offset < p_ponit.offset; PRESET_LINEAR,
} PRESET_EASE_IN,
PRESET_EASE_OUT,
PRESET_SMOOTHSTEP,
PRESET_COUNT
}; };
bool grabbing; enum ContextAction {
int grabbed; CONTEXT_ADD_POINT = 0,
Vector<Point> points; CONTEXT_REMOVE_POINT
float max, min; };
void _plot_curve(const Vector2 &p_a, const Vector2 &p_b, const Vector2 &p_c, const Vector2 &p_d); enum TangentIndex {
TANGENT_NONE = -1,
TANGENT_LEFT = 0,
TANGENT_RIGHT = 1
};
protected: protected:
void _gui_input(const Ref<InputEvent> &p_event);
void _notification(int p_what); void _notification(int p_what);
static void _bind_methods(); static void _bind_methods();
public: private:
void set_range(float p_min, float p_max); void on_gui_input(const Ref<InputEvent> &p_event);
void set_points(const Vector<Vector2> &p_points); void on_preset_item_selected(int preset_id);
Vector<Vector2> get_points() const; void _curve_changed();
virtual Size2 get_minimum_size() const; void on_context_menu_item_selected(int action_id);
CurveTextureEdit();
void open_context_menu(Vector2 pos);
int get_point_at(Vector2 pos) const;
int get_tangent_at(Vector2 pos) const;
void add_point(Vector2 pos);
void remove_point(int index);
void set_selected_point(int index);
void set_hover_point_index(int index);
void push_undo(Array previous_curve_data);
void update_view_transform();
Vector2 get_tangent_view_pos(int i, TangentIndex tangent) const;
Vector2 get_view_pos(Vector2 world_pos) const;
Vector2 get_world_pos(Vector2 view_pos) const;
void _draw();
void stroke_rect(Rect2 rect, Color color);
private:
Rect2 _world_rect;
Transform2D _world_to_view;
Ref<Curve> _curve_ref;
PopupMenu *_context_menu;
PopupMenu *_presets_menu;
Array _undo_data;
bool _has_undo_data;
bool _undo_no_commit;
Vector2 _context_click_pos;
int _selected_point;
int _hover_point;
int _selected_tangent;
bool _dragging;
// Constant
float _hover_radius;
float _tangents_length;
}; };
class CurveTextureEditorPlugin : public EditorPlugin { class CurveEditorPlugin : public EditorPlugin {
GDCLASS(CurveEditorPlugin, EditorPlugin)
GDCLASS(CurveTextureEditorPlugin, EditorPlugin);
CurveTextureEdit *curve_editor;
Ref<CurveTexture> curve_texture_ref;
EditorNode *editor;
ToolButton *curve_button;
protected:
static void _bind_methods();
void _curve_changed();
void _undo_redo_curve_texture(const PoolVector<Vector2> &points);
void _curve_settings_changed();
public: public:
virtual String get_name() const { return "CurveTexture"; } CurveEditorPlugin(EditorNode *p_node);
bool has_main_screen() const { return false; } ~CurveEditorPlugin();
virtual void edit(Object *p_node);
virtual bool handles(Object *p_node) const;
virtual void make_visible(bool p_visible);
CurveTextureEditorPlugin(EditorNode *p_node); String get_name() const { return "Curve"; }
~CurveTextureEditorPlugin(); bool has_main_screen() const { return false; }
void edit(Object *p_object);
bool handles(Object *p_object) const;
void make_visible(bool p_visible);
private:
static void _bind_methods();
void _curve_texture_changed();
private:
CurveEditor *_view;
Ref<Resource> _current_ref;
EditorNode *_editor_node;
ToolButton *_toggle_button;
}; };
#endif // CURVE_EDITOR_PLUGIN_H #endif // CURVE_EDITOR_PLUGIN_H

View File

@ -61,9 +61,21 @@ void TextureEditor::_notification(int p_what) {
tex_height = texture->get_height() * tex_width / texture->get_width(); tex_height = texture->get_height() * tex_width / texture->get_width();
} }
// Prevent the texture from being unpreviewable after the rescale, so that we can still see something
if (tex_height <= 0)
tex_height = 1;
if (tex_width <= 0)
tex_width = 1;
int ofs_x = (size.width - tex_width) / 2; int ofs_x = (size.width - tex_width) / 2;
int ofs_y = (size.height - tex_height) / 2; int ofs_y = (size.height - tex_height) / 2;
if (texture->cast_to<CurveTexture>()) {
// In the case of CurveTextures we know they are 1 in height, so fill the preview to see the gradient
ofs_y = 0;
tex_height = size.height;
}
draw_texture_rect(texture, Rect2(ofs_x, ofs_y, tex_width, tex_height)); draw_texture_rect(texture, Rect2(ofs_x, ofs_y, tex_width, tex_height));
Ref<Font> font = get_font("font", "Label"); Ref<Font> font = get_font("font", "Label");

View File

@ -1082,16 +1082,9 @@ void ParticlesMaterial::set_param_texture(Parameter p_param, const Ref<Texture>
case PARAM_SCALE: { case PARAM_SCALE: {
VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_texture, p_texture); VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_texture, p_texture);
Ref<CurveTexture> curve = p_texture; Ref<CurveTexture> curve_tex = p_texture;
if (curve.is_valid()) { if (curve_tex.is_valid()) {
if (curve->get_min() == 0 && curve->get_max() == 1) { curve_tex->ensure_default_setup();
curve->set_max(32);
PoolVector<Vector2> points;
points.push_back(Vector2(0, 1));
points.push_back(Vector2(1, 1));
curve->set_points(points);
}
} }
} break; } break;
@ -1257,14 +1250,7 @@ void ParticlesMaterial::set_trail_size_modifier(const Ref<CurveTexture> &p_trail
Ref<CurveTexture> curve = trail_size_modifier; Ref<CurveTexture> curve = trail_size_modifier;
if (curve.is_valid()) { if (curve.is_valid()) {
if (curve->get_min() == 0 && curve->get_max() == 1) { curve->ensure_default_setup();
curve->set_max(32);
PoolVector<Vector2> points;
points.push_back(Vector2(0, 1));
points.push_back(Vector2(1, 1));
curve->set_points(points);
}
} }
RID texture; RID texture;

View File

@ -580,6 +580,7 @@ void register_scene_types() {
ClassDB::register_class<Animation>(); ClassDB::register_class<Animation>();
ClassDB::register_virtual_class<Font>(); ClassDB::register_virtual_class<Font>();
ClassDB::register_class<BitmapFont>(); ClassDB::register_class<BitmapFont>();
ClassDB::register_class<Curve>();
ClassDB::register_class<DynamicFontData>(); ClassDB::register_class<DynamicFontData>();
ClassDB::register_class<DynamicFont>(); ClassDB::register_class<DynamicFont>();

View File

@ -380,6 +380,365 @@ Curve2D::Curve2D()
#endif #endif
Curve::Curve() {
_bake_resolution = 100;
_baked_cache_dirty = false;
#ifdef TOOLS_ENABLED
_disable_set_data = false;
#endif
}
int Curve::add_point(Vector2 p_pos, real_t left_tangent, real_t right_tangent) {
// Add a point and preserve order
// Curve bounds is in 0..1
if (p_pos.x > MAX_X)
p_pos.x = MAX_X;
else if (p_pos.x < MIN_X)
p_pos.x = MIN_X;
int ret = -1;
if (_points.size() == 0) {
_points.push_back(Point(p_pos, left_tangent, right_tangent));
ret = 0;
} else if (_points.size() == 1) {
// TODO Is the `else` able to handle this block already?
real_t diff = p_pos.x - _points[0].pos.x;
if (diff > 0) {
_points.push_back(Point(p_pos, left_tangent, right_tangent));
ret = 1;
} else {
_points.insert(0, Point(p_pos, left_tangent, right_tangent));
ret = 0;
}
} else {
int i = get_index(p_pos.x);
int nearest_index = i;
if (i + 1 < _points.size()) {
real_t diff0 = p_pos.x - _points[i].pos.x;
real_t diff1 = _points[i + 1].pos.x - p_pos.x;
if (diff1 < diff0)
nearest_index = i + 1;
}
if (i == 0 && p_pos.x < _points[0].pos.x) {
// Insert before anything else
_points.insert(0, Point(p_pos, left_tangent, right_tangent));
ret = 0;
} else {
// Insert between i and i+1
++i;
_points.insert(i, Point(p_pos, left_tangent, right_tangent));
ret = i;
}
}
mark_dirty();
return ret;
}
int Curve::get_index(real_t offset) const {
// Lower-bound float binary search
int imin = 0;
int imax = _points.size() - 1;
while (imax - imin > 1) {
int m = (imin + imax) / 2;
real_t a = _points[m].pos.x;
real_t b = _points[m + 1].pos.x;
if (a < offset && b < offset) {
imin = m;
} else if (a > offset) {
imax = m;
} else {
return m;
}
}
// Will happen if the offset is out of bounds
if (offset > _points[imax].pos.x)
return imax;
return imin;
}
void Curve::clean_dupes() {
bool dirty = false;
for (int i = 1; i < _points.size(); ++i) {
real_t diff = _points[i - 1].pos.x - _points[i].pos.x;
if (diff <= CMP_EPSILON) {
_points.remove(i);
--i;
dirty = true;
}
}
if (dirty)
mark_dirty();
}
void Curve::set_point_left_tangent(int i, real_t tangent) {
ERR_FAIL_INDEX(i, _points.size());
_points[i].left_tangent = tangent;
mark_dirty();
}
void Curve::set_point_right_tangent(int i, real_t tangent) {
ERR_FAIL_INDEX(i, _points.size());
_points[i].right_tangent = tangent;
mark_dirty();
}
real_t Curve::get_point_left_tangent(int i) const {
ERR_FAIL_INDEX_V(i, _points.size(), 0);
return _points[i].left_tangent;
}
real_t Curve::get_point_right_tangent(int i) const {
ERR_FAIL_INDEX_V(i, _points.size(), 0);
return _points[i].right_tangent;
}
void Curve::remove_point(int p_index) {
ERR_FAIL_INDEX(p_index, _points.size());
_points.remove(p_index);
mark_dirty();
}
void Curve::clear_points() {
_points.clear();
mark_dirty();
}
void Curve::set_point_value(int p_index, real_t pos) {
ERR_FAIL_INDEX(p_index, _points.size());
_points[p_index].pos.y = pos;
mark_dirty();
}
int Curve::set_point_offset(int p_index, float offset) {
ERR_FAIL_INDEX_V(p_index, _points.size(), -1);
Point p = _points[p_index];
remove_point(p_index);
int i = add_point(Vector2(offset, p.pos.y));
_points[i].left_tangent = p.left_tangent;
_points[i].right_tangent = p.right_tangent;
return i;
}
Vector2 Curve::get_point_pos(int p_index) const {
ERR_FAIL_INDEX_V(p_index, _points.size(), Vector2(0, 0));
return _points[p_index].pos;
}
real_t Curve::interpolate(real_t offset) const {
if (_points.size() == 0)
return 0;
if (_points.size() == 1)
return _points[0].pos.y;
int i = get_index(offset);
if (i == _points.size() - 1)
return _points[i].pos.y;
real_t local = offset - _points[i].pos.x;
if (i == 0 && local <= 0)
return _points[0].pos.y;
return interpolate_local_nocheck(i, local);
}
real_t Curve::interpolate_local_nocheck(int index, real_t local_offset) const {
const Point a = _points[index];
const Point b = _points[index + 1];
// Cubic bezier
// ac-----bc
// / \
// / \ Here with a.right_tangent > 0
// / \ and b.left_tangent < 0
// / \
// a b
//
// |-d1--|-d2--|-d3--|
//
// d1 == d2 == d3 == d / 3
// Control points are chosen at equal distances
real_t d = b.pos.x - a.pos.x;
if (Math::abs(d) <= CMP_EPSILON)
return b.pos.y;
local_offset /= d;
d /= 3.0;
real_t yac = a.pos.y + d * a.right_tangent;
real_t ybc = b.pos.y - d * b.left_tangent;
real_t y = _bezier_interp(local_offset, a.pos.y, yac, ybc, b.pos.y);
return y;
}
void Curve::mark_dirty() {
_baked_cache_dirty = true;
emit_signal(CoreStringNames::get_singleton()->changed);
}
Array Curve::get_data() const {
Array output;
output.resize(_points.size() * 3);
for (int j = 0; j < _points.size(); ++j) {
const Point p = _points[j];
int i = j * 3;
output[i] = p.pos;
output[i + 1] = p.left_tangent;
output[i + 2] = p.right_tangent;
}
return output;
}
void Curve::set_data(Array input) {
ERR_FAIL_COND(input.size() % 3 != 0);
#ifdef TOOLS_ENABLED
if (_disable_set_data)
return;
#endif
_points.clear();
// Validate input
for (int i = 0; i < input.size(); i += 3) {
ERR_FAIL_COND(input[i].get_type() != Variant::VECTOR2);
ERR_FAIL_COND(input[i + 1].get_type() != Variant::REAL);
ERR_FAIL_COND(input[i + 2].get_type() != Variant::REAL);
}
_points.resize(input.size() / 3);
for (int j = 0; j < _points.size(); ++j) {
Point &p = _points[j];
int i = j * 3;
p.pos = input[i];
p.left_tangent = input[i + 1];
p.right_tangent = input[i + 2];
}
mark_dirty();
}
void Curve::bake() {
_baked_cache.clear();
_baked_cache.resize(_bake_resolution);
for (int i = 1; i < _bake_resolution - 1; ++i) {
real_t x = i / static_cast<real_t>(_bake_resolution);
real_t y = interpolate(x);
_baked_cache[i] = y;
}
if (_points.size() != 0) {
_baked_cache[0] = _points[0].pos.y;
_baked_cache[_baked_cache.size() - 1] = _points[_points.size() - 1].pos.y;
}
_baked_cache_dirty = false;
}
void Curve::set_bake_resolution(int p_resolution) {
ERR_FAIL_COND(p_resolution < 1);
ERR_FAIL_COND(p_resolution > 1000);
_bake_resolution = p_resolution;
_baked_cache_dirty = true;
}
real_t Curve::interpolate_baked(real_t offset) {
if (_baked_cache_dirty) {
// Last-second bake if not done already
bake();
}
// Special cases if the cache is too small
if (_baked_cache.size() == 0) {
if (_points.size() == 0)
return 0;
return _points[0].pos.y;
} else if (_baked_cache.size() == 1) {
return _baked_cache[0];
}
// Get interpolation index
real_t fi = offset * _baked_cache.size();
int i = Math::floor(fi);
if (i < 0) {
i = 0;
fi = 0;
} else if (i >= _baked_cache.size()) {
i = _baked_cache.size() - 1;
fi = 0;
}
// Interpolate
if (i + 1 < _baked_cache.size()) {
real_t t = fi - i;
return Math::lerp(_baked_cache[i], _baked_cache[i + 1], t);
} else {
return _baked_cache[_baked_cache.size() - 1];
}
}
void Curve::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_point", "pos", "left_tangent", "right_tangent"), &Curve::add_point, DEFVAL(0), DEFVAL(0));
ClassDB::bind_method(D_METHOD("remove_point", "index"), &Curve::remove_point);
ClassDB::bind_method(D_METHOD("clear_points"), &Curve::clear_points);
ClassDB::bind_method(D_METHOD("get_point_pos", "index"), &Curve::get_point_pos);
ClassDB::bind_method(D_METHOD("set_point_value", "index, y"), &Curve::set_point_value);
ClassDB::bind_method(D_METHOD("set_point_offset", "index, offset"), &Curve::set_point_value);
ClassDB::bind_method(D_METHOD("interpolate", "offset"), &Curve::interpolate);
ClassDB::bind_method(D_METHOD("interpolate_baked", "offset"), &Curve::interpolate_baked);
ClassDB::bind_method(D_METHOD("get_point_left_tangent", "index"), &Curve::get_point_left_tangent);
ClassDB::bind_method(D_METHOD("get_point_right_tangent", "index"), &Curve::get_point_left_tangent);
ClassDB::bind_method(D_METHOD("set_point_left_tangent", "index", "tangent"), &Curve::set_point_left_tangent);
ClassDB::bind_method(D_METHOD("set_point_right_tangent", "index", "tangent"), &Curve::set_point_left_tangent);
ClassDB::bind_method(D_METHOD("clean_dupes"), &Curve::clean_dupes);
ClassDB::bind_method(D_METHOD("bake"), &Curve::bake);
ClassDB::bind_method(D_METHOD("get_bake_resolution"), &Curve::get_bake_resolution);
ClassDB::bind_method(D_METHOD("set_bake_resolution", "resolution"), &Curve::set_bake_resolution);
ClassDB::bind_method(D_METHOD("_get_data"), &Curve::get_data);
ClassDB::bind_method(D_METHOD("_set_data", "data"), &Curve::set_data);
ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_resolution", PROPERTY_HINT_RANGE, "1,1000,1"), "set_bake_resolution", "get_bake_resolution");
ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "_set_data", "_get_data");
}
int Curve2D::get_point_count() const { int Curve2D::get_point_count() const {
return points.size(); return points.size();

View File

@ -82,6 +82,78 @@ public:
#endif #endif
// y(x) curve
class Curve : public Resource {
GDCLASS(Curve, Resource)
public:
static const int MIN_X = 0.f;
static const int MAX_X = 1.f;
#ifdef TOOLS_ENABLED
bool _disable_set_data;
#endif
struct Point {
Vector2 pos;
real_t left_tangent;
real_t right_tangent;
Point() {
left_tangent = 0;
right_tangent = 0;
}
Point(Vector2 p, real_t left = 0, real_t right = 0) {
pos = p;
left_tangent = left;
right_tangent = right;
}
};
Curve();
int get_point_count() const { return _points.size(); }
int add_point(Vector2 p_pos, real_t left_tangent = 0, real_t right_tangent = 0);
void remove_point(int p_index);
void clear_points();
int get_index(real_t offset) const;
void set_point_value(int p_index, real_t pos);
int set_point_offset(int p_index, float offset);
Vector2 get_point_pos(int p_index) const;
real_t interpolate(real_t offset) const;
real_t interpolate_local_nocheck(int index, real_t local_offset) const;
void clean_dupes();
void set_point_left_tangent(int i, real_t tangent);
void set_point_right_tangent(int i, real_t tangent);
real_t get_point_left_tangent(int i) const;
real_t get_point_right_tangent(int i) const;
Array get_data() const;
void set_data(Array input);
void bake();
int get_bake_resolution() const { return _bake_resolution; }
void set_bake_resolution(int p_interval);
real_t interpolate_baked(real_t offset);
protected:
static void _bind_methods();
private:
void mark_dirty();
Vector<Point> _points;
bool _baked_cache_dirty;
Vector<real_t> _baked_cache;
int _bake_resolution;
};
class Curve2D : public Resource { class Curve2D : public Resource {
GDCLASS(Curve2D, Resource); GDCLASS(Curve2D, Resource);

View File

@ -30,7 +30,9 @@
#include "texture.h" #include "texture.h"
#include "core/method_bind_ext.inc" #include "core/method_bind_ext.inc"
#include "core/os/os.h" #include "core/os/os.h"
#include "core_string_names.h"
#include "io/image_loader.h" #include "io/image_loader.h"
Size2 Texture::get_size() const { Size2 Texture::get_size() const {
return Size2(get_width(), get_height()); return Size2(get_width(), get_height());
@ -1376,254 +1378,126 @@ void CurveTexture::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_width", "width"), &CurveTexture::set_width); ClassDB::bind_method(D_METHOD("set_width", "width"), &CurveTexture::set_width);
ClassDB::bind_method(D_METHOD("set_points", "points"), &CurveTexture::set_points); ClassDB::bind_method(D_METHOD("set_curve", "curve:Curve"), &CurveTexture::set_curve);
ClassDB::bind_method(D_METHOD("get_points"), &CurveTexture::get_points); ClassDB::bind_method(D_METHOD("get_curve:Curve"), &CurveTexture::get_curve);
ClassDB::bind_method(D_METHOD("_update"), &CurveTexture::_update);
ADD_PROPERTY(PropertyInfo(Variant::REAL, "min", PROPERTY_HINT_RANGE, "-1024,1024"), "set_min", "get_min"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "min", PROPERTY_HINT_RANGE, "-1024,1024"), "set_min", "get_min");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "max", PROPERTY_HINT_RANGE, "-1024,1024"), "set_max", "get_max"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "max", PROPERTY_HINT_RANGE, "-1024,1024"), "set_max", "get_max");
ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "32,4096"), "set_width", "get_width"); ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "32,4096"), "set_width", "get_width");
ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "points"), "set_points", "get_points"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve");
} }
void CurveTexture::set_max(float p_max) { void CurveTexture::set_max(float p_max) {
max = p_max; _max = p_max;
emit_changed(); emit_changed();
} }
float CurveTexture::get_max() const { float CurveTexture::get_max() const {
return max; return _max;
} }
void CurveTexture::set_min(float p_min) { void CurveTexture::set_min(float p_min) {
min = p_min; _min = p_min;
emit_changed(); emit_changed();
} }
float CurveTexture::get_min() const { float CurveTexture::get_min() const {
return min; return _min;
} }
void CurveTexture::set_width(int p_width) { void CurveTexture::set_width(int p_width) {
ERR_FAIL_COND(p_width < 32 || p_width > 4096); ERR_FAIL_COND(p_width < 32 || p_width > 4096);
width = p_width; _width = p_width;
if (points.size()) _update();
set_points(points);
} }
int CurveTexture::get_width() const { int CurveTexture::get_width() const {
return width; return _width;
} }
static void _plot_curve(const Vector2 &p_a, const Vector2 &p_b, const Vector2 &p_c, const Vector2 &p_d, float *p_heights, bool *p_useds, int p_width, float p_min, float p_max) { void CurveTexture::ensure_default_setup() {
float geometry[4][4]; if (_curve.is_null()) {
float tmp1[4][4]; Ref<Curve> curve = Ref<Curve>(memnew(Curve));
float tmp2[4][4]; curve->add_point(Vector2(0, 1));
float deltas[4][4]; curve->add_point(Vector2(1, 1));
double x, dx, dx2, dx3; set_curve(curve);
double y, dy, dy2, dy3;
double d, d2, d3;
int lastx;
int newx;
float lasty;
float newy;
int ntimes;
int i, j;
int xmax = p_width;
/* construct the geometry matrix from the segment */
for (i = 0; i < 4; i++) {
geometry[i][2] = 0;
geometry[i][3] = 0;
} }
geometry[0][0] = (p_a[0] * xmax); if (get_min() == 0 && get_max() == 1) {
geometry[1][0] = (p_b[0] * xmax); set_max(32);
geometry[2][0] = (p_c[0] * xmax);
geometry[3][0] = (p_d[0] * xmax);
geometry[0][1] = (p_a[1]);
geometry[1][1] = (p_b[1]);
geometry[2][1] = (p_c[1]);
geometry[3][1] = (p_d[1]);
/* subdivide the curve ntimes (1000) times */
ntimes = 4 * xmax;
/* ntimes can be adjusted to give a finer or coarser curve */
d = 1.0 / ntimes;
d2 = d * d;
d3 = d * d * d;
/* construct a temporary matrix for determining the forward differencing deltas */
tmp2[0][0] = 0;
tmp2[0][1] = 0;
tmp2[0][2] = 0;
tmp2[0][3] = 1;
tmp2[1][0] = d3;
tmp2[1][1] = d2;
tmp2[1][2] = d;
tmp2[1][3] = 0;
tmp2[2][0] = 6 * d3;
tmp2[2][1] = 2 * d2;
tmp2[2][2] = 0;
tmp2[2][3] = 0;
tmp2[3][0] = 6 * d3;
tmp2[3][1] = 0;
tmp2[3][2] = 0;
tmp2[3][3] = 0;
/* compose the basis and geometry matrices */
static const float CR_basis[4][4] = {
{ -0.5, 1.5, -1.5, 0.5 },
{ 1.0, -2.5, 2.0, -0.5 },
{ -0.5, 0.0, 0.5, 0.0 },
{ 0.0, 1.0, 0.0, 0.0 },
};
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) {
tmp1[i][j] = (CR_basis[i][0] * geometry[0][j] +
CR_basis[i][1] * geometry[1][j] +
CR_basis[i][2] * geometry[2][j] +
CR_basis[i][3] * geometry[3][j]);
}
}
/* compose the above results to get the deltas matrix */
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) {
deltas[i][j] = (tmp2[i][0] * tmp1[0][j] +
tmp2[i][1] * tmp1[1][j] +
tmp2[i][2] * tmp1[2][j] +
tmp2[i][3] * tmp1[3][j]);
}
}
/* extract the x deltas */
x = deltas[0][0];
dx = deltas[1][0];
dx2 = deltas[2][0];
dx3 = deltas[3][0];
/* extract the y deltas */
y = deltas[0][1];
dy = deltas[1][1];
dy2 = deltas[2][1];
dy3 = deltas[3][1];
lastx = CLAMP(x, 0, xmax);
lasty = y;
p_heights[lastx] = lasty;
p_useds[lastx] = true;
/* loop over the curve */
for (i = 0; i < ntimes; i++) {
/* increment the x values */
x += dx;
dx += dx2;
dx2 += dx3;
/* increment the y values */
y += dy;
dy += dy2;
dy2 += dy3;
newx = CLAMP((Math::round(x)), 0, xmax);
newy = CLAMP(y, p_min, p_max);
/* if this point is different than the last one...then draw it */
if ((lastx != newx) || (lasty != newy)) {
p_useds[newx] = true;
p_heights[newx] = newy;
}
lastx = newx;
lasty = newy;
} }
} }
void CurveTexture::set_points(const PoolVector<Vector2> &p_points) { void CurveTexture::set_curve(Ref<Curve> p_curve) {
if (_curve != p_curve) {
if (_curve.is_valid()) {
_curve->disconnect(CoreStringNames::get_singleton()->changed, this, "_update");
}
_curve = p_curve;
if (_curve.is_valid()) {
_curve->connect(CoreStringNames::get_singleton()->changed, this, "_update");
}
_update();
}
}
points = p_points; void CurveTexture::_update() {
PoolVector<uint8_t> data; PoolVector<uint8_t> data;
PoolVector<bool> used; data.resize(_width * sizeof(float));
data.resize(width * sizeof(float));
used.resize(width); // The array is locked in that scope
{ {
PoolVector<uint8_t>::Write wd8 = data.write(); PoolVector<uint8_t>::Write wd8 = data.write();
float *wd = (float *)wd8.ptr(); float *wd = (float *)wd8.ptr();
PoolVector<bool>::Write wu = used.write();
int pc = p_points.size();
PoolVector<Vector2>::Read pr = p_points.read();
for (int i = 0; i < width; i++) { if (_curve.is_valid()) {
wd[i] = 0.0; Curve &curve = **_curve;
wu[i] = false; float height = _max - _min;
} for (int i = 0; i < _width; ++i) {
float t = i / static_cast<float>(_width);
Vector2 prev = Vector2(0, 0); float v = curve.interpolate_baked(t);
Vector2 prev2 = Vector2(0, 0); wd[i] = CLAMP(_min + v * height, _min, _max);
for (int i = -1; i < pc; i++) {
Vector2 next;
Vector2 next2;
if (i + 1 >= pc) {
next = Vector2(1, 0);
} else {
next = Vector2(pr[i + 1].x, pr[i + 1].y);
} }
if (i + 2 >= pc) { } else {
next2 = Vector2(1, 0); for (int i = 0; i < _width; ++i) {
} else { wd[i] = 0;
next2 = Vector2(pr[i + 2].x, pr[i + 2].y);
} }
/*if (i==-1 && prev.offset==next.offset) {
prev=next;
continue;
}*/
_plot_curve(prev2, prev, next, next2, wd, wu.ptr(), width, min, max);
prev2 = prev;
prev = next;
} }
} }
Ref<Image> image = memnew(Image(width, 1, false, Image::FORMAT_RF, data)); Ref<Image> image = memnew(Image(_width, 1, false, Image::FORMAT_RF, data));
VS::get_singleton()->texture_allocate(texture, width, 1, Image::FORMAT_RF, VS::TEXTURE_FLAG_FILTER); VS::get_singleton()->texture_allocate(_texture, _width, 1, Image::FORMAT_RF, VS::TEXTURE_FLAG_FILTER);
VS::get_singleton()->texture_set_data(texture, image); VS::get_singleton()->texture_set_data(_texture, image);
emit_changed(); emit_changed();
} }
PoolVector<Vector2> CurveTexture::get_points() const { Ref<Curve> CurveTexture::get_curve() const {
return points; return _curve;
} }
RID CurveTexture::get_rid() const { RID CurveTexture::get_rid() const {
return texture; return _texture;
} }
CurveTexture::CurveTexture() { CurveTexture::CurveTexture() {
max = 1; _max = 1;
min = 0; _min = 0;
width = 2048; _width = 2048;
texture = VS::get_singleton()->texture_create(); _texture = VS::get_singleton()->texture_create();
} }
CurveTexture::~CurveTexture() { CurveTexture::~CurveTexture() {
VS::get_singleton()->free(texture); VS::get_singleton()->free(_texture);
} }
////////////////// //////////////////

View File

@ -30,6 +30,7 @@
#ifndef TEXTURE_H #ifndef TEXTURE_H
#define TEXTURE_H #define TEXTURE_H
#include "curve.h"
#include "io/resource_loader.h" #include "io/resource_loader.h"
#include "math_2d.h" #include "math_2d.h"
#include "resource.h" #include "resource.h"
@ -389,20 +390,22 @@ public:
~CubeMap(); ~CubeMap();
}; };
VARIANT_ENUM_CAST(CubeMap::Flags); VARIANT_ENUM_CAST(CubeMap::Flags)
VARIANT_ENUM_CAST(CubeMap::Side); VARIANT_ENUM_CAST(CubeMap::Side)
VARIANT_ENUM_CAST(CubeMap::Storage); VARIANT_ENUM_CAST(CubeMap::Storage)
class CurveTexture : public Texture { class CurveTexture : public Texture {
GDCLASS(CurveTexture, Texture); GDCLASS(CurveTexture, Texture)
RES_BASE_EXTENSION("curvetex"); RES_BASE_EXTENSION("curvetex")
private: private:
RID texture; RID _texture;
PoolVector<Vector2> points; Ref<Curve> _curve;
float min, max; float _min, _max;
int width; int _width;
void _update();
protected: protected:
static void _bind_methods(); static void _bind_methods();
@ -417,8 +420,10 @@ public:
void set_width(int p_width); void set_width(int p_width);
int get_width() const; int get_width() const;
void set_points(const PoolVector<Vector2> &p_points); void ensure_default_setup();
PoolVector<Vector2> get_points() const;
void set_curve(Ref<Curve> p_curve);
Ref<Curve> get_curve() const;
virtual RID get_rid() const; virtual RID get_rid() const;