summaryrefslogtreecommitdiff
path: root/scene/resources
diff options
context:
space:
mode:
Diffstat (limited to 'scene/resources')
-rw-r--r--scene/resources/animation.cpp1046
-rw-r--r--scene/resources/animation.h83
-rw-r--r--scene/resources/audio_stream_sample.cpp8
-rw-r--r--scene/resources/curve.cpp291
-rw-r--r--scene/resources/curve.h10
-rw-r--r--scene/resources/default_theme/default_theme.cpp12
-rw-r--r--scene/resources/dynamic_font.cpp538
-rw-r--r--scene/resources/dynamic_font.h37
-rw-r--r--scene/resources/dynamic_font_stb.cpp22
-rw-r--r--scene/resources/dynamic_font_stb.h4
-rw-r--r--scene/resources/font.cpp43
-rw-r--r--scene/resources/font.h49
-rw-r--r--scene/resources/material.cpp8
-rw-r--r--scene/resources/material.h3
-rw-r--r--scene/resources/mesh.cpp9
-rw-r--r--scene/resources/primitive_meshes.cpp60
-rw-r--r--scene/resources/primitive_meshes.h8
-rw-r--r--scene/resources/style_box.cpp17
-rw-r--r--scene/resources/texture.cpp11
-rw-r--r--scene/resources/texture.h2
-rw-r--r--scene/resources/tile_set.cpp23
-rw-r--r--scene/resources/tile_set.h8
22 files changed, 1984 insertions, 308 deletions
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index 7a1fffaa26..fe4d687c23 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -32,6 +32,8 @@
#include "geometry.h"
+#define ANIM_MIN_LENGTH 0.001
+
bool Animation::_set(const StringName &p_name, const Variant &p_value) {
String name = p_name;
@@ -54,6 +56,15 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
} else if (type == "method") {
add_track(TYPE_METHOD);
+ } else if (type == "bezier") {
+
+ add_track(TYPE_BEZIER);
+ } else if (type == "audio") {
+
+ add_track(TYPE_AUDIO);
+ } else if (type == "animation") {
+
+ add_track(TYPE_ANIMATION);
} else {
return false;
@@ -163,7 +174,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
return true;
- } else {
+ } else if (track_get_type(track) == TYPE_METHOD) {
while (track_get_key_count(track))
track_remove_key(track, 0); //well shouldn't be set anyway
@@ -201,6 +212,114 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
}
}
}
+ } else if (track_get_type(track) == TYPE_BEZIER) {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(tracks[track]);
+ Dictionary d = p_value;
+ ERR_FAIL_COND_V(!d.has("times"), false);
+ ERR_FAIL_COND_V(!d.has("points"), false);
+
+ PoolVector<float> times = d["times"];
+ PoolRealArray values = d["points"];
+
+ ERR_FAIL_COND_V(times.size() * 5 != values.size(), false);
+
+ if (times.size()) {
+
+ int valcount = times.size();
+
+ PoolVector<float>::Read rt = times.read();
+ PoolVector<float>::Read rv = values.read();
+
+ bt->values.resize(valcount);
+
+ for (int i = 0; i < valcount; i++) {
+
+ bt->values[i].time = rt[i];
+ bt->values[i].transition = 0; //unused in bezier
+ bt->values[i].value.value = rv[i * 5 + 0];
+ bt->values[i].value.in_handle.x = rv[i * 5 + 1];
+ bt->values[i].value.in_handle.y = rv[i * 5 + 2];
+ bt->values[i].value.out_handle.x = rv[i * 5 + 3];
+ bt->values[i].value.out_handle.y = rv[i * 5 + 4];
+ }
+ }
+
+ return true;
+ } else if (track_get_type(track) == TYPE_AUDIO) {
+
+ AudioTrack *ad = static_cast<AudioTrack *>(tracks[track]);
+ Dictionary d = p_value;
+ ERR_FAIL_COND_V(!d.has("times"), false);
+ ERR_FAIL_COND_V(!d.has("clips"), false);
+
+ PoolVector<float> times = d["times"];
+ Array clips = d["clips"];
+
+ ERR_FAIL_COND_V(clips.size() != times.size(), false);
+
+ if (times.size()) {
+
+ int valcount = times.size();
+
+ PoolVector<float>::Read rt = times.read();
+
+ ad->values.clear();
+
+ for (int i = 0; i < valcount; i++) {
+
+ Dictionary d = clips[i];
+ if (!d.has("start_offset"))
+ continue;
+ if (!d.has("end_offset"))
+ continue;
+ if (!d.has("stream"))
+ continue;
+
+ TKey<AudioKey> ak;
+ ak.time = rt[i];
+ ak.value.start_offset = d["start_offset"];
+ ak.value.end_offset = d["end_offset"];
+ ak.value.stream = d["stream"];
+
+ ad->values.push_back(ak);
+ }
+ }
+
+ return true;
+ } else if (track_get_type(track) == TYPE_ANIMATION) {
+
+ AnimationTrack *an = static_cast<AnimationTrack *>(tracks[track]);
+ Dictionary d = p_value;
+ ERR_FAIL_COND_V(!d.has("times"), false);
+ ERR_FAIL_COND_V(!d.has("clips"), false);
+
+ PoolVector<float> times = d["times"];
+ PoolVector<String> clips = d["clips"];
+
+ ERR_FAIL_COND_V(clips.size() != times.size(), false);
+
+ if (times.size()) {
+
+ int valcount = times.size();
+
+ PoolVector<float>::Read rt = times.read();
+ PoolVector<String>::Read rc = clips.read();
+
+ an->values.resize(valcount);
+
+ for (int i = 0; i < valcount; i++) {
+
+ TKey<StringName> ak;
+ ak.time = rt[i];
+ ak.value = rc[i];
+ an->values[i] = ak;
+ }
+ }
+
+ return true;
+ } else {
+ return false;
}
} else
return false;
@@ -232,6 +351,9 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
case TYPE_TRANSFORM: r_ret = "transform"; break;
case TYPE_VALUE: r_ret = "value"; break;
case TYPE_METHOD: r_ret = "method"; break;
+ case TYPE_BEZIER: r_ret = "bezier"; break;
+ case TYPE_AUDIO: r_ret = "audio"; break;
+ case TYPE_ANIMATION: r_ret = "animation"; break;
}
return true;
@@ -329,7 +451,7 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
return true;
- } else {
+ } else if (track_get_type(track) == TYPE_METHOD) {
Dictionary d;
@@ -368,6 +490,119 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
r_ret = d;
return true;
+ } else if (track_get_type(track) == TYPE_BEZIER) {
+
+ const BezierTrack *bt = static_cast<const BezierTrack *>(tracks[track]);
+
+ Dictionary d;
+
+ PoolVector<float> key_times;
+ PoolVector<float> key_points;
+
+ int kk = bt->values.size();
+
+ key_times.resize(kk);
+ key_points.resize(kk * 5);
+
+ PoolVector<float>::Write wti = key_times.write();
+ PoolVector<float>::Write wpo = key_points.write();
+
+ int idx = 0;
+
+ const TKey<BezierKey> *vls = bt->values.ptr();
+
+ for (int i = 0; i < kk; i++) {
+
+ wti[idx] = vls[i].time;
+ wpo[idx * 5 + 0] = vls[i].value.value;
+ wpo[idx * 5 + 1] = vls[i].value.in_handle.x;
+ wpo[idx * 5 + 2] = vls[i].value.in_handle.y;
+ wpo[idx * 5 + 3] = vls[i].value.out_handle.x;
+ wpo[idx * 5 + 4] = vls[i].value.out_handle.y;
+ idx++;
+ }
+
+ wti = PoolVector<float>::Write();
+ wpo = PoolVector<float>::Write();
+
+ d["times"] = key_times;
+ d["points"] = key_points;
+
+ r_ret = d;
+
+ return true;
+ } else if (track_get_type(track) == TYPE_AUDIO) {
+
+ const AudioTrack *ad = static_cast<const AudioTrack *>(tracks[track]);
+
+ Dictionary d;
+
+ PoolVector<float> key_times;
+ Array clips;
+
+ int kk = ad->values.size();
+
+ key_times.resize(kk);
+
+ PoolVector<float>::Write wti = key_times.write();
+
+ int idx = 0;
+
+ const TKey<AudioKey> *vls = ad->values.ptr();
+
+ for (int i = 0; i < kk; i++) {
+
+ wti[idx] = vls[i].time;
+ Dictionary clip;
+ clip["start_offset"] = vls[i].value.start_offset;
+ clip["end_offset"] = vls[i].value.end_offset;
+ clip["stream"] = vls[i].value.stream;
+ clips.push_back(clip);
+ idx++;
+ }
+
+ wti = PoolVector<float>::Write();
+
+ d["times"] = key_times;
+ d["clips"] = clips;
+
+ r_ret = d;
+
+ return true;
+ } else if (track_get_type(track) == TYPE_ANIMATION) {
+
+ const AnimationTrack *an = static_cast<const AnimationTrack *>(tracks[track]);
+
+ Dictionary d;
+
+ PoolVector<float> key_times;
+ PoolVector<String> clips;
+
+ int kk = an->values.size();
+
+ key_times.resize(kk);
+ clips.resize(kk);
+
+ PoolVector<float>::Write wti = key_times.write();
+ PoolVector<String>::Write wcl = clips.write();
+
+ const TKey<StringName> *vls = an->values.ptr();
+
+ for (int i = 0; i < kk; i++) {
+
+ wti[i] = vls[i].time;
+ wcl[i] = vls[i].value;
+ }
+
+ wti = PoolVector<float>::Write();
+ wcl = PoolVector<String>::Write();
+
+ d["times"] = key_times;
+ d["clips"] = clips;
+
+ r_ret = d;
+
+ return true;
}
} else
return false;
@@ -412,6 +647,21 @@ int Animation::add_track(TrackType p_type, int p_at_pos) {
tracks.insert(p_at_pos, memnew(MethodTrack));
} break;
+ case TYPE_BEZIER: {
+
+ tracks.insert(p_at_pos, memnew(BezierTrack));
+
+ } break;
+ case TYPE_AUDIO: {
+
+ tracks.insert(p_at_pos, memnew(AudioTrack));
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ tracks.insert(p_at_pos, memnew(AnimationTrack));
+
+ } break;
default: {
ERR_PRINT("Unknown track type");
@@ -446,6 +696,24 @@ void Animation::remove_track(int p_track) {
_clear(mt->methods);
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bz = static_cast<BezierTrack *>(t);
+ _clear(bz->values);
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *ad = static_cast<AudioTrack *>(t);
+ _clear(ad->values);
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *an = static_cast<AnimationTrack *>(t);
+ _clear(an->values);
+
+ } break;
}
memdelete(t);
@@ -642,6 +910,27 @@ void Animation::track_remove_key(int p_track, int p_idx) {
mt->methods.remove(p_idx);
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bz = static_cast<BezierTrack *>(t);
+ ERR_FAIL_INDEX(p_idx, bz->values.size());
+ bz->values.remove(p_idx);
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *ad = static_cast<AudioTrack *>(t);
+ ERR_FAIL_INDEX(p_idx, ad->values.size());
+ ad->values.remove(p_idx);
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *an = static_cast<AnimationTrack *>(t);
+ ERR_FAIL_INDEX(p_idx, an->values.size());
+ an->values.remove(p_idx);
+
+ } break;
}
emit_changed();
@@ -686,6 +975,39 @@ int Animation::track_find_key(int p_track, float p_time, bool p_exact) const {
return k;
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+ int k = _find(bt->values, p_time);
+ if (k < 0 || k >= bt->values.size())
+ return -1;
+ if (bt->values[k].time != p_time && p_exact)
+ return -1;
+ return k;
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+ int k = _find(at->values, p_time);
+ if (k < 0 || k >= at->values.size())
+ return -1;
+ if (at->values[k].time != p_time && p_exact)
+ return -1;
+ return k;
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+ int k = _find(at->values, p_time);
+ if (k < 0 || k >= at->values.size())
+ return -1;
+ if (at->values[k].time != p_time && p_exact)
+ return -1;
+ return k;
+
+ } break;
}
return -1;
@@ -748,6 +1070,51 @@ void Animation::track_insert_key(int p_track, float p_time, const Variant &p_key
_insert(p_time, mt->methods, k);
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ Array arr = p_key;
+ ERR_FAIL_COND(arr.size() != 5);
+
+ TKey<BezierKey> k;
+ k.time = p_time;
+ k.value.value = arr[0];
+ k.value.in_handle.x = arr[1];
+ k.value.in_handle.y = arr[2];
+ k.value.out_handle.x = arr[3];
+ k.value.out_handle.y = arr[4];
+ _insert(p_time, bt->values, k);
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ Dictionary k = p_key;
+ ERR_FAIL_COND(!k.has("start_offset"));
+ ERR_FAIL_COND(!k.has("end_offset"));
+ ERR_FAIL_COND(!k.has("stream"));
+
+ TKey<AudioKey> ak;
+ ak.time = p_time;
+ ak.value.start_offset = k["start_offset"];
+ ak.value.end_offset = k["end_offset"];
+ ak.value.stream = k["stream"];
+ _insert(p_time, at->values, ak);
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+
+ TKey<StringName> ak;
+ ak.time = p_time;
+ ak.value = p_key;
+
+ _insert(p_time, at->values, ak);
+
+ } break;
}
emit_changed();
@@ -776,6 +1143,21 @@ int Animation::track_get_key_count(int p_track) const {
MethodTrack *mt = static_cast<MethodTrack *>(t);
return mt->methods.size();
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+ return bt->values.size();
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+ return at->values.size();
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+ return at->values.size();
+ } break;
}
ERR_FAIL_V(-1);
@@ -817,6 +1199,41 @@ Variant Animation::track_get_key_value(int p_track, int p_key_idx) const {
return d;
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+ ERR_FAIL_INDEX_V(p_key_idx, bt->values.size(), Variant());
+
+ Array arr;
+ arr.resize(5);
+ arr[0] = bt->values[p_key_idx].value.value;
+ arr[1] = bt->values[p_key_idx].value.in_handle.x;
+ arr[2] = bt->values[p_key_idx].value.in_handle.y;
+ arr[3] = bt->values[p_key_idx].value.out_handle.x;
+ arr[4] = bt->values[p_key_idx].value.out_handle.y;
+ return arr;
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+ ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), Variant());
+
+ Dictionary k;
+ k["start_offset"] = at->values[p_key_idx].value.start_offset;
+ k["end_offset"] = at->values[p_key_idx].value.end_offset;
+ k["stream"] = at->values[p_key_idx].value.stream;
+ return k;
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+ ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), Variant());
+
+ return at->values[p_key_idx].value;
+
+ } break;
}
ERR_FAIL_V(Variant());
@@ -849,6 +1266,27 @@ float Animation::track_get_key_time(int p_track, int p_key_idx) const {
return mt->methods[p_key_idx].time;
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+ ERR_FAIL_INDEX_V(p_key_idx, bt->values.size(), -1);
+ return bt->values[p_key_idx].time;
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+ ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), -1);
+ return at->values[p_key_idx].time;
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+ ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), -1);
+ return at->values[p_key_idx].time;
+
+ } break;
}
ERR_FAIL_V(-1);
@@ -881,6 +1319,18 @@ float Animation::track_get_key_transition(int p_track, int p_key_idx) const {
return mt->methods[p_key_idx].transition;
} break;
+ case TYPE_BEZIER: {
+
+ return 1; //bezier does not really use transitions
+ } break;
+ case TYPE_AUDIO: {
+
+ return 1; //audio does not really use transitions
+ } break;
+ case TYPE_ANIMATION: {
+
+ return 1; //animation does not really use transitions
+ } break;
}
ERR_FAIL_V(0);
@@ -923,6 +1373,42 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p
if (d.has("args"))
mt->methods[p_key_idx].params = d["args"];
} break;
+ case TYPE_BEZIER: {
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+ ERR_FAIL_INDEX(p_key_idx, bt->values.size());
+
+ Array arr = p_value;
+ ERR_FAIL_COND(arr.size() != 5);
+
+ bt->values[p_key_idx].value.value = arr[0];
+ bt->values[p_key_idx].value.in_handle.x = arr[1];
+ bt->values[p_key_idx].value.in_handle.y = arr[2];
+ bt->values[p_key_idx].value.out_handle.x = arr[3];
+ bt->values[p_key_idx].value.out_handle.y = arr[4];
+
+ } break;
+ case TYPE_AUDIO: {
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ Dictionary k = p_value;
+ ERR_FAIL_COND(!k.has("start_offset"));
+ ERR_FAIL_COND(!k.has("end_offset"));
+ ERR_FAIL_COND(!k.has("stream"));
+
+ at->values[p_key_idx].value.start_offset = k["start_offset"];
+ at->values[p_key_idx].value.end_offset = k["end_offset"];
+ at->values[p_key_idx].value.stream = k["stream"];
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+
+ at->values[p_key_idx].value = p_value;
+
+ } break;
}
}
@@ -953,6 +1439,11 @@ void Animation::track_set_key_transition(int p_track, int p_key_idx, float p_tra
mt->methods[p_key_idx].transition = p_transition;
} break;
+ case TYPE_BEZIER:
+ case TYPE_AUDIO:
+ case TYPE_ANIMATION: {
+ // they dont use transition
+ } break;
}
}
@@ -1410,7 +1901,7 @@ void Animation::value_track_set_update_mode(int p_track, UpdateMode p_mode) {
ERR_FAIL_INDEX(p_track, tracks.size());
Track *t = tracks[p_track];
ERR_FAIL_COND(t->type != TYPE_VALUE);
- ERR_FAIL_INDEX(p_mode, 3);
+ ERR_FAIL_INDEX(p_mode, 4);
ValueTrack *vt = static_cast<ValueTrack *>(t);
vt->update_mode = p_mode;
@@ -1426,6 +1917,161 @@ Animation::UpdateMode Animation::value_track_get_update_mode(int p_track) const
return vt->update_mode;
}
+template <class T>
+void Animation::_track_get_key_indices_in_range(const Vector<T> &p_array, float from_time, float to_time, List<int> *p_indices) const {
+
+ if (from_time != length && to_time == length)
+ to_time = length * 1.01; //include a little more if at the end
+
+ int to = _find(p_array, to_time);
+
+ // can't really send the events == time, will be sent in the next frame.
+ // if event>=len then it will probably never be requested by the anim player.
+
+ if (to >= 0 && p_array[to].time >= to_time)
+ to--;
+
+ if (to < 0)
+ return; // not bother
+
+ int from = _find(p_array, from_time);
+
+ // position in the right first event.+
+ if (from < 0 || p_array[from].time < from_time)
+ from++;
+
+ int max = p_array.size();
+
+ for (int i = from; i <= to; i++) {
+
+ ERR_CONTINUE(i < 0 || i >= max); // shouldn't happen
+ p_indices->push_back(i);
+ }
+}
+
+void Animation::track_get_key_indices_in_range(int p_track, float p_time, float p_delta, List<int> *p_indices) const {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ const Track *t = tracks[p_track];
+
+ float from_time = p_time - p_delta;
+ float to_time = p_time;
+
+ if (from_time > to_time)
+ SWAP(from_time, to_time);
+
+ if (loop) {
+
+ if (from_time > length || from_time < 0)
+ from_time = Math::fposmod(from_time, length);
+
+ if (to_time > length || to_time < 0)
+ to_time = Math::fposmod(to_time, length);
+
+ if (from_time > to_time) {
+ // handle loop by splitting
+
+ switch (t->type) {
+
+ case TYPE_TRANSFORM: {
+
+ const TransformTrack *tt = static_cast<const TransformTrack *>(t);
+ _track_get_key_indices_in_range(tt->transforms, from_time, length, p_indices);
+ _track_get_key_indices_in_range(tt->transforms, 0, to_time, p_indices);
+
+ } break;
+ case TYPE_VALUE: {
+
+ const ValueTrack *vt = static_cast<const ValueTrack *>(t);
+ _track_get_key_indices_in_range(vt->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(vt->values, 0, to_time, p_indices);
+
+ } break;
+ case TYPE_METHOD: {
+
+ const MethodTrack *mt = static_cast<const MethodTrack *>(t);
+ _track_get_key_indices_in_range(mt->methods, from_time, length, p_indices);
+ _track_get_key_indices_in_range(mt->methods, 0, to_time, p_indices);
+
+ } break;
+ case TYPE_BEZIER: {
+
+ const BezierTrack *bz = static_cast<const BezierTrack *>(t);
+ _track_get_key_indices_in_range(bz->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(bz->values, 0, to_time, p_indices);
+
+ } break;
+ case TYPE_AUDIO: {
+
+ const AudioTrack *ad = static_cast<const AudioTrack *>(t);
+ _track_get_key_indices_in_range(ad->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(ad->values, 0, to_time, p_indices);
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
+ _track_get_key_indices_in_range(an->values, from_time, length, p_indices);
+ _track_get_key_indices_in_range(an->values, 0, to_time, p_indices);
+
+ } break;
+ }
+ return;
+ }
+ } else {
+
+ if (from_time < 0)
+ from_time = 0;
+ if (from_time > length)
+ from_time = length;
+
+ if (to_time < 0)
+ to_time = 0;
+ if (to_time > length)
+ to_time = length;
+ }
+
+ switch (t->type) {
+
+ case TYPE_TRANSFORM: {
+
+ const TransformTrack *tt = static_cast<const TransformTrack *>(t);
+ _track_get_key_indices_in_range(tt->transforms, from_time, to_time, p_indices);
+
+ } break;
+ case TYPE_VALUE: {
+
+ const ValueTrack *vt = static_cast<const ValueTrack *>(t);
+ _track_get_key_indices_in_range(vt->values, from_time, to_time, p_indices);
+
+ } break;
+ case TYPE_METHOD: {
+
+ const MethodTrack *mt = static_cast<const MethodTrack *>(t);
+ _track_get_key_indices_in_range(mt->methods, from_time, to_time, p_indices);
+
+ } break;
+ case TYPE_BEZIER: {
+
+ const BezierTrack *bz = static_cast<const BezierTrack *>(t);
+ _track_get_key_indices_in_range(bz->values, from_time, to_time, p_indices);
+
+ } break;
+ case TYPE_AUDIO: {
+
+ const AudioTrack *ad = static_cast<const AudioTrack *>(t);
+ _track_get_key_indices_in_range(ad->values, from_time, to_time, p_indices);
+
+ } break;
+ case TYPE_ANIMATION: {
+
+ const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
+ _track_get_key_indices_in_range(an->values, from_time, to_time, p_indices);
+
+ } break;
+ }
+}
+
void Animation::_method_track_get_key_indices_in_range(const MethodTrack *mt, float from_time, float to_time, List<int> *p_indices) const {
if (from_time != length && to_time == length)
@@ -1527,9 +2173,362 @@ StringName Animation::method_track_get_name(int p_track, int p_key_idx) const {
return pm->methods[p_key_idx].method;
}
+int Animation::bezier_track_insert_key(int p_track, float p_time, float p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle) {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), -1);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_BEZIER, -1);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ TKey<BezierKey> k;
+ k.time = p_time;
+ k.value.value = p_value;
+ k.value.in_handle = p_in_handle;
+ if (k.value.in_handle.x > 0) {
+ k.value.in_handle.x = 0;
+ }
+ k.value.out_handle = p_out_handle;
+ if (k.value.out_handle.x < 0) {
+ k.value.out_handle.x = 0;
+ }
+
+ int key = _insert(p_time, bt->values, k);
+
+ emit_changed();
+
+ return key;
+}
+
+void Animation::bezier_track_set_key_value(int p_track, int p_index, float p_value) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_BEZIER);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX(p_index, bt->values.size());
+
+ bt->values[p_index].value.value = p_value;
+ emit_changed();
+}
+
+void Animation::bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_BEZIER);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX(p_index, bt->values.size());
+
+ bt->values[p_index].value.in_handle = p_handle;
+ if (bt->values[p_index].value.in_handle.x > 0) {
+ bt->values[p_index].value.in_handle.x = 0;
+ }
+ emit_changed();
+}
+void Animation::bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_BEZIER);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX(p_index, bt->values.size());
+
+ bt->values[p_index].value.out_handle = p_handle;
+ if (bt->values[p_index].value.out_handle.x < 0) {
+ bt->values[p_index].value.out_handle.x = 0;
+ }
+ emit_changed();
+}
+float Animation::bezier_track_get_key_value(int p_track, int p_index) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), 0);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_BEZIER, 0);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_index, bt->values.size(), 0);
+
+ return bt->values[p_index].value.value;
+}
+Vector2 Animation::bezier_track_get_key_in_handle(int p_track, int p_index) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), Vector2());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_BEZIER, Vector2());
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_index, bt->values.size(), Vector2());
+
+ return bt->values[p_index].value.in_handle;
+}
+Vector2 Animation::bezier_track_get_key_out_handle(int p_track, int p_index) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), Vector2());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_BEZIER, Vector2());
+
+ BezierTrack *bt = static_cast<BezierTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_index, bt->values.size(), Vector2());
+
+ return bt->values[p_index].value.out_handle;
+}
+
+static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, const Vector2 &start, const Vector2 &control_1, const Vector2 &control_2, const Vector2 &end) {
+ /* Formula from Wikipedia article on Bezier curves. */
+ real_t omt = (1.0 - t);
+ real_t omt2 = omt * omt;
+ real_t omt3 = omt2 * omt;
+ real_t t2 = t * t;
+ real_t t3 = t2 * t;
+
+ return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3;
+}
+
+float Animation::bezier_track_interpolate(int p_track, float p_time) const {
+ //this uses a different interpolation scheme
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), 0);
+ Track *track = tracks[p_track];
+ ERR_FAIL_COND_V(track->type != TYPE_BEZIER, 0);
+
+ BezierTrack *bt = static_cast<BezierTrack *>(track);
+
+ int len = _find(bt->values, length) + 1; // try to find last key (there may be more past the end)
+
+ if (len <= 0) {
+ // (-1 or -2 returned originally) (plus one above)
+ return 0;
+ } else if (len == 1) { // one key found (0+1), return it
+ return bt->values[0].value.value;
+ }
+
+ int idx = _find(bt->values, p_time);
+
+ ERR_FAIL_COND_V(idx == -2, 0);
+
+ //there really is no looping interpolation on bezier
+
+ if (idx < 0) {
+ return bt->values[0].value.value;
+ }
+
+ if (idx >= bt->values.size() - 1) {
+ return bt->values[bt->values.size() - 1].value.value;
+ }
+
+ float t = p_time - bt->values[idx].time;
+
+ int iterations = 10;
+
+ float low = 0;
+ float high = bt->values[idx + 1].time - bt->values[idx].time;
+ float middle = 0;
+
+ Vector2 start(0, bt->values[idx].value.value);
+ Vector2 start_out = start + bt->values[idx].value.out_handle;
+ Vector2 end(high, bt->values[idx + 1].value.value);
+ Vector2 end_in = end + bt->values[idx + 1].value.in_handle;
+
+ //narrow high and low as much as possible
+ for (int i = 0; i < iterations; i++) {
+
+ middle = (low + high) / 2;
+
+ Vector2 interp = _bezier_interp(middle, start, start_out, end_in, end);
+
+ if (interp.x < t) {
+ low = middle;
+ } else {
+ high = middle;
+ }
+ }
+
+ //interpolate the result:
+ Vector2 low_pos = _bezier_interp(low, start, start_out, end_in, end);
+ Vector2 high_pos = _bezier_interp(high, start, start_out, end_in, end);
+
+ float c = (t - low_pos.x) / (high_pos.x - low_pos.x);
+
+ return low_pos.linear_interpolate(high_pos, c).y;
+}
+
+int Animation::audio_track_insert_key(int p_track, float p_time, const RES &p_stream, float p_start_offset, float p_end_offset) {
+
+ print_line("really insert key? ");
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), -1);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_AUDIO, -1);
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ TKey<AudioKey> k;
+ k.time = p_time;
+ k.value.stream = p_stream;
+ k.value.start_offset = p_start_offset;
+ if (k.value.start_offset < 0)
+ k.value.start_offset = 0;
+ k.value.end_offset = p_end_offset;
+ if (k.value.end_offset < 0)
+ k.value.end_offset = 0;
+
+ int key = _insert(p_time, at->values, k);
+
+ emit_changed();
+
+ return key;
+}
+
+void Animation::audio_track_set_key_stream(int p_track, int p_key, const RES &p_stream) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_AUDIO);
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ ERR_FAIL_INDEX(p_key, at->values.size());
+
+ at->values[p_key].value.stream = p_stream;
+
+ emit_changed();
+}
+
+void Animation::audio_track_set_key_start_offset(int p_track, int p_key, float p_offset) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_AUDIO);
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ ERR_FAIL_INDEX(p_key, at->values.size());
+
+ if (p_offset < 0)
+ p_offset = 0;
+
+ at->values[p_key].value.start_offset = p_offset;
+
+ emit_changed();
+}
+
+void Animation::audio_track_set_key_end_offset(int p_track, int p_key, float p_offset) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_AUDIO);
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ ERR_FAIL_INDEX(p_key, at->values.size());
+
+ if (p_offset < 0)
+ p_offset = 0;
+
+ at->values[p_key].value.end_offset = p_offset;
+
+ emit_changed();
+}
+
+RES Animation::audio_track_get_key_stream(int p_track, int p_key) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), RES());
+ const Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_AUDIO, RES());
+
+ const AudioTrack *at = static_cast<const AudioTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_key, at->values.size(), RES());
+
+ return at->values[p_key].value.stream;
+}
+float Animation::audio_track_get_key_start_offset(int p_track, int p_key) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), 0);
+ const Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_AUDIO, 0);
+
+ const AudioTrack *at = static_cast<const AudioTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_key, at->values.size(), 0);
+
+ return at->values[p_key].value.start_offset;
+}
+float Animation::audio_track_get_key_end_offset(int p_track, int p_key) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), 0);
+ const Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_AUDIO, 0);
+
+ const AudioTrack *at = static_cast<const AudioTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_key, at->values.size(), 0);
+
+ return at->values[p_key].value.end_offset;
+}
+
+//
+
+int Animation::animation_track_insert_key(int p_track, float p_time, const StringName &p_animation) {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), -1);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_ANIMATION, -1);
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+
+ TKey<StringName> k;
+ k.time = p_time;
+ k.value = p_animation;
+
+ int key = _insert(p_time, at->values, k);
+
+ emit_changed();
+
+ return key;
+}
+
+void Animation::animation_track_set_key_animation(int p_track, int p_key, const StringName &p_animation) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_ANIMATION);
+
+ AnimationTrack *at = static_cast<AnimationTrack *>(t);
+
+ ERR_FAIL_INDEX(p_key, at->values.size());
+
+ at->values[p_key].value = p_animation;
+
+ emit_changed();
+}
+
+StringName Animation::animation_track_get_key_animation(int p_track, int p_key) const {
+
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), StringName());
+ const Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_ANIMATION, StringName());
+
+ const AnimationTrack *at = static_cast<const AnimationTrack *>(t);
+
+ ERR_FAIL_INDEX_V(p_key, at->values.size(), StringName());
+
+ return at->values[p_key].value;
+}
+
void Animation::set_length(float p_length) {
- ERR_FAIL_COND(length < 0);
+ if (p_length < ANIM_MIN_LENGTH) {
+ p_length = ANIM_MIN_LENGTH;
+ }
length = p_length;
emit_changed();
}
@@ -1592,6 +2591,16 @@ void Animation::track_move_down(int p_track) {
emit_changed();
}
+void Animation::track_swap(int p_track, int p_with_track) {
+
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ ERR_FAIL_INDEX(p_with_track, tracks.size());
+ if (p_track == p_with_track)
+ return;
+ SWAP(tracks[p_track], tracks[p_with_track]);
+ emit_changed();
+}
+
void Animation::set_step(float p_step) {
step = p_step;
@@ -1631,6 +2640,7 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("track_move_up", "idx"), &Animation::track_move_up);
ClassDB::bind_method(D_METHOD("track_move_down", "idx"), &Animation::track_move_down);
+ ClassDB::bind_method(D_METHOD("track_swap", "idx", "with_idx"), &Animation::track_swap);
ClassDB::bind_method(D_METHOD("track_set_imported", "idx", "imported"), &Animation::track_set_imported);
ClassDB::bind_method(D_METHOD("track_is_imported", "idx"), &Animation::track_is_imported);
@@ -1667,6 +2677,30 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("method_track_get_name", "idx", "key_idx"), &Animation::method_track_get_name);
ClassDB::bind_method(D_METHOD("method_track_get_params", "idx", "key_idx"), &Animation::method_track_get_params);
+ ClassDB::bind_method(D_METHOD("bezier_track_insert_key", "track", "time", "value", "in_handle", "out_handle"), &Animation::bezier_track_insert_key, DEFVAL(Vector2()), DEFVAL(Vector2()));
+
+ ClassDB::bind_method(D_METHOD("bezier_track_set_key_value", "idx", "key_idx", "value"), &Animation::bezier_track_set_key_value);
+ ClassDB::bind_method(D_METHOD("bezier_track_set_key_in_handle", "idx", "key_idx", "in_handle"), &Animation::bezier_track_set_key_in_handle);
+ ClassDB::bind_method(D_METHOD("bezier_track_set_key_out_handle", "idx", "key_idx", "out_handle"), &Animation::bezier_track_set_key_out_handle);
+
+ ClassDB::bind_method(D_METHOD("bezier_track_get_key_value", "idx", "key_idx"), &Animation::bezier_track_get_key_value);
+ ClassDB::bind_method(D_METHOD("bezier_track_get_key_in_handle", "idx", "key_idx"), &Animation::bezier_track_get_key_in_handle);
+ ClassDB::bind_method(D_METHOD("bezier_track_get_key_out_handle", "idx", "key_idx"), &Animation::bezier_track_get_key_out_handle);
+
+ ClassDB::bind_method(D_METHOD("bezier_track_interpolate", "track", "time"), &Animation::bezier_track_interpolate);
+
+ ClassDB::bind_method(D_METHOD("audio_track_insert_key", "track", "time", "stream", "start_offset", "end_offset"), &Animation::audio_track_insert_key, DEFVAL(0), DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("audio_track_set_key_stream", "idx", "key_idx", "stream"), &Animation::audio_track_set_key_stream);
+ ClassDB::bind_method(D_METHOD("audio_track_set_key_start_offset", "idx", "key_idx", "offset"), &Animation::audio_track_set_key_start_offset);
+ ClassDB::bind_method(D_METHOD("audio_track_set_key_end_offset", "idx", "key_idx", "offset"), &Animation::audio_track_set_key_end_offset);
+ ClassDB::bind_method(D_METHOD("audio_track_get_key_stream", "idx", "key_idx"), &Animation::audio_track_get_key_stream);
+ ClassDB::bind_method(D_METHOD("audio_track_get_key_start_offset", "idx", "key_idx"), &Animation::audio_track_get_key_start_offset);
+ ClassDB::bind_method(D_METHOD("audio_track_get_key_end_offset", "idx", "key_idx"), &Animation::audio_track_get_key_end_offset);
+
+ ClassDB::bind_method(D_METHOD("animation_track_insert_key", "track", "time", "animation"), &Animation::animation_track_insert_key);
+ ClassDB::bind_method(D_METHOD("animation_track_set_key_animation", "idx", "key_idx", "animation"), &Animation::animation_track_set_key_animation);
+ ClassDB::bind_method(D_METHOD("animation_track_get_key_animation", "idx", "key_idx"), &Animation::animation_track_get_key_animation);
+
ClassDB::bind_method(D_METHOD("set_length", "time_sec"), &Animation::set_length);
ClassDB::bind_method(D_METHOD("get_length"), &Animation::get_length);
@@ -1686,6 +2720,9 @@ void Animation::_bind_methods() {
BIND_ENUM_CONSTANT(TYPE_VALUE);
BIND_ENUM_CONSTANT(TYPE_TRANSFORM);
BIND_ENUM_CONSTANT(TYPE_METHOD);
+ BIND_ENUM_CONSTANT(TYPE_BEZIER);
+ BIND_ENUM_CONSTANT(TYPE_AUDIO);
+ BIND_ENUM_CONSTANT(TYPE_ANIMATION);
BIND_ENUM_CONSTANT(INTERPOLATION_NEAREST);
BIND_ENUM_CONSTANT(INTERPOLATION_LINEAR);
@@ -1694,6 +2731,7 @@ void Animation::_bind_methods() {
BIND_ENUM_CONSTANT(UPDATE_CONTINUOUS);
BIND_ENUM_CONSTANT(UPDATE_DISCRETE);
BIND_ENUM_CONSTANT(UPDATE_TRIGGER);
+ BIND_ENUM_CONSTANT(UPDATE_CAPTURE);
}
void Animation::clear() {
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index 73691a69f2..a41e6ea5d7 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -45,6 +45,9 @@ public:
TYPE_VALUE, ///< Set a value in a property, can be interpolated.
TYPE_TRANSFORM, ///< Transform a node or a bone.
TYPE_METHOD, ///< Call any method on a specific node.
+ TYPE_BEZIER, ///< Bezier curve
+ TYPE_AUDIO,
+ TYPE_ANIMATION,
};
enum InterpolationType {
@@ -57,6 +60,7 @@ public:
UPDATE_CONTINUOUS,
UPDATE_DISCRETE,
UPDATE_TRIGGER,
+ UPDATE_CAPTURE,
};
@@ -137,6 +141,55 @@ private:
MethodTrack() { type = TYPE_METHOD; }
};
+ /* BEZIER TRACK */
+
+ struct BezierKey {
+ Vector2 in_handle; //relative (x always <0)
+ Vector2 out_handle; //relative (x always >0)
+ float value;
+ };
+
+ struct BezierTrack : public Track {
+
+ Vector<TKey<BezierKey> > values;
+
+ BezierTrack() {
+ type = TYPE_BEZIER;
+ }
+ };
+
+ /* AUDIO TRACK */
+
+ struct AudioKey {
+ RES stream;
+ float start_offset; //offset from start
+ float end_offset; //offset from end, if 0 then full length or infinite
+ AudioKey() {
+ start_offset = 0;
+ end_offset = 0;
+ }
+ };
+
+ struct AudioTrack : public Track {
+
+ Vector<TKey<AudioKey> > values;
+
+ AudioTrack() {
+ type = TYPE_AUDIO;
+ }
+ };
+
+ /* AUDIO TRACK */
+
+ struct AnimationTrack : public Track {
+
+ Vector<TKey<StringName> > values;
+
+ AnimationTrack() {
+ type = TYPE_ANIMATION;
+ }
+ };
+
Vector<Track *> tracks;
/*
@@ -168,6 +221,9 @@ private:
template <class T>
_FORCE_INLINE_ T _interpolate(const Vector<TKey<T> > &p_keys, float p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok) const;
+ template <class T>
+ _FORCE_INLINE_ void _track_get_key_indices_in_range(const Vector<T> &p_array, float from_time, float to_time, List<int> *p_indices) const;
+
_FORCE_INLINE_ void _value_track_get_key_indices_in_range(const ValueTrack *vt, float from_time, float to_time, List<int> *p_indices) const;
_FORCE_INLINE_ void _method_track_get_key_indices_in_range(const MethodTrack *mt, float from_time, float to_time, List<int> *p_indices) const;
@@ -238,6 +294,7 @@ public:
void track_move_up(int p_track);
void track_move_down(int p_track);
+ void track_swap(int p_track, int p_with_track);
void track_set_imported(int p_track, bool p_imported);
bool track_is_imported(int p_track) const;
@@ -245,7 +302,6 @@ public:
void track_set_enabled(int p_track, bool p_enabled);
bool track_is_enabled(int p_track) const;
- int transform_track_insert_key(int p_track, float p_time, const Vector3 p_loc, const Quat &p_rot = Quat(), const Vector3 &p_scale = Vector3());
void track_insert_key(int p_track, float p_time, const Variant &p_key, float p_transition = 1);
void track_set_key_transition(int p_track, int p_key_idx, float p_transition);
void track_set_key_value(int p_track, int p_key_idx, const Variant &p_value);
@@ -257,10 +313,33 @@ public:
float track_get_key_time(int p_track, int p_key_idx) const;
float track_get_key_transition(int p_track, int p_key_idx) const;
+ int transform_track_insert_key(int p_track, float p_time, const Vector3 p_loc, const Quat &p_rot = Quat(), const Vector3 &p_scale = Vector3());
Error transform_track_get_key(int p_track, int p_key, Vector3 *r_loc, Quat *r_rot, Vector3 *r_scale) const;
void track_set_interpolation_type(int p_track, InterpolationType p_interp);
InterpolationType track_get_interpolation_type(int p_track) const;
+ int bezier_track_insert_key(int p_track, float p_time, float p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle);
+ void bezier_track_set_key_value(int p_track, int p_index, float p_value);
+ void bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle);
+ void bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle);
+ float bezier_track_get_key_value(int p_track, int p_index) const;
+ Vector2 bezier_track_get_key_in_handle(int p_track, int p_index) const;
+ Vector2 bezier_track_get_key_out_handle(int p_track, int p_index) const;
+
+ float bezier_track_interpolate(int p_track, float p_time) const;
+
+ int audio_track_insert_key(int p_track, float p_time, const RES &p_stream, float p_start_offset = 0, float p_end_offset = 0);
+ void audio_track_set_key_stream(int p_track, int p_key, const RES &p_stream);
+ void audio_track_set_key_start_offset(int p_track, int p_key, float p_offset);
+ void audio_track_set_key_end_offset(int p_track, int p_key, float p_offset);
+ RES audio_track_get_key_stream(int p_track, int p_key) const;
+ float audio_track_get_key_start_offset(int p_track, int p_key) const;
+ float audio_track_get_key_end_offset(int p_track, int p_key) const;
+
+ int animation_track_insert_key(int p_track, float p_time, const StringName &p_animation);
+ void animation_track_set_key_animation(int p_track, int p_key, const StringName &p_animation);
+ StringName animation_track_get_key_animation(int p_track, int p_key) const;
+
void track_set_interpolation_loop_wrap(int p_track, bool p_enable);
bool track_get_interpolation_loop_wrap(int p_track) const;
@@ -277,6 +356,8 @@ public:
void copy_track(int p_track, Ref<Animation> p_to_animation);
+ void track_get_key_indices_in_range(int p_track, float p_time, float p_delta, List<int> *p_indices) const;
+
void set_length(float p_length);
float get_length() const;
diff --git a/scene/resources/audio_stream_sample.cpp b/scene/resources/audio_stream_sample.cpp
index b77143cd9d..02a9e4d69b 100644
--- a/scene/resources/audio_stream_sample.cpp
+++ b/scene/resources/audio_stream_sample.cpp
@@ -524,6 +524,9 @@ String AudioStreamSample::get_stream_name() const {
void AudioStreamSample::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_data", "data"), &AudioStreamSample::set_data);
+ ClassDB::bind_method(D_METHOD("get_data"), &AudioStreamSample::get_data);
+
ClassDB::bind_method(D_METHOD("set_format", "format"), &AudioStreamSample::set_format);
ClassDB::bind_method(D_METHOD("get_format"), &AudioStreamSample::get_format);
@@ -542,16 +545,13 @@ void AudioStreamSample::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_stereo", "stereo"), &AudioStreamSample::set_stereo);
ClassDB::bind_method(D_METHOD("is_stereo"), &AudioStreamSample::is_stereo);
- ClassDB::bind_method(D_METHOD("_set_data", "data"), &AudioStreamSample::set_data);
- ClassDB::bind_method(D_METHOD("_get_data"), &AudioStreamSample::get_data);
-
+ ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_data", "get_data");
ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM"), "set_format", "get_format");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "Disabled,Forward,Ping-Pong"), "set_loop_mode", "get_loop_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_begin"), "set_loop_begin", "get_loop_begin");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_end"), "set_loop_end", "get_loop_end");
ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_rate"), "set_mix_rate", "get_mix_rate");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stereo"), "set_stereo", "is_stereo");
- ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
BIND_ENUM_CONSTANT(FORMAT_8_BITS);
BIND_ENUM_CONSTANT(FORMAT_16_BITS);
diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp
index 5fd6f6c74d..7f902fc982 100644
--- a/scene/resources/curve.cpp
+++ b/scene/resources/curve.cpp
@@ -806,6 +806,87 @@ float Curve2D::get_bake_interval() const {
return bake_interval;
}
+Vector2 Curve2D::get_closest_point(const Vector2 &p_to_point) const {
+ // Brute force method
+
+ if (baked_cache_dirty)
+ _bake();
+
+ //validate//
+ int pc = baked_point_cache.size();
+ if (pc == 0) {
+ ERR_EXPLAIN("No points in Curve2D");
+ ERR_FAIL_COND_V(pc == 0, Vector2());
+ }
+
+ if (pc == 1)
+ return baked_point_cache.get(0);
+
+ PoolVector2Array::Read r = baked_point_cache.read();
+
+ Vector2 nearest;
+ float nearest_dist = -1.0f;
+
+ for (int i = 0; i < pc - 1; i++) {
+ Vector2 origin = r[i];
+ Vector2 direction = (r[i + 1] - origin) / bake_interval;
+
+ float d = CLAMP((p_to_point - origin).dot(direction), 0.0f, bake_interval);
+ Vector2 proj = origin + direction * d;
+
+ float dist = proj.distance_squared_to(p_to_point);
+
+ if (nearest_dist < 0.0f || dist < nearest_dist) {
+ nearest = proj;
+ nearest_dist = dist;
+ }
+ }
+
+ return nearest;
+}
+
+float Curve2D::get_closest_offset(const Vector2 &p_to_point) const {
+ // Brute force method
+
+ if (baked_cache_dirty)
+ _bake();
+
+ //validate//
+ int pc = baked_point_cache.size();
+ if (pc == 0) {
+ ERR_EXPLAIN("No points in Curve2D");
+ ERR_FAIL_COND_V(pc == 0, 0.0f);
+ }
+
+ if (pc == 1)
+ return 0.0f;
+
+ PoolVector2Array::Read r = baked_point_cache.read();
+
+ float nearest = 0.0f;
+ float nearest_dist = -1.0f;
+ float offset = 0.0f;
+
+ for (int i = 0; i < pc - 1; i++) {
+ Vector2 origin = r[i];
+ Vector2 direction = (r[i + 1] - origin) / bake_interval;
+
+ float d = CLAMP((p_to_point - origin).dot(direction), 0.0f, bake_interval);
+ Vector2 proj = origin + direction * d;
+
+ float dist = proj.distance_squared_to(p_to_point);
+
+ if (nearest_dist < 0.0f || dist < nearest_dist) {
+ nearest = offset + d;
+ nearest_dist = dist;
+ }
+
+ offset += bake_interval;
+ }
+
+ return nearest;
+}
+
Dictionary Curve2D::_get_data() const {
Dictionary dc;
@@ -909,6 +990,8 @@ void Curve2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_baked_length"), &Curve2D::get_baked_length);
ClassDB::bind_method(D_METHOD("interpolate_baked", "offset", "cubic"), &Curve2D::interpolate_baked, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_baked_points"), &Curve2D::get_baked_points);
+ ClassDB::bind_method(D_METHOD("get_closest_point", "to_point"), &Curve2D::get_closest_point);
+ ClassDB::bind_method(D_METHOD("get_closest_offset", "to_point"), &Curve2D::get_closest_offset);
ClassDB::bind_method(D_METHOD("tessellate", "max_stages", "tolerance_degrees"), &Curve2D::tessellate, DEFVAL(5), DEFVAL(4));
ClassDB::bind_method(D_METHOD("_get_data"), &Curve2D::_get_data);
@@ -1086,6 +1169,7 @@ void Curve3D::_bake() const {
if (points.size() == 0) {
baked_point_cache.resize(0);
baked_tilt_cache.resize(0);
+ baked_up_vector_cache.resize(0);
return;
}
@@ -1095,6 +1179,14 @@ void Curve3D::_bake() const {
baked_point_cache.set(0, points[0].pos);
baked_tilt_cache.resize(1);
baked_tilt_cache.set(0, points[0].tilt);
+
+ if (up_vector_enabled) {
+
+ baked_up_vector_cache.resize(1);
+ baked_up_vector_cache.set(0, Vector3(0, 1, 0));
+ } else
+ baked_up_vector_cache.resize(0);
+
return;
}
@@ -1164,10 +1256,51 @@ void Curve3D::_bake() const {
baked_tilt_cache.resize(pointlist.size());
PoolRealArray::Write wt = baked_tilt_cache.write();
+ baked_up_vector_cache.resize(up_vector_enabled ? pointlist.size() : 0);
+ PoolVector3Array::Write up_write = baked_up_vector_cache.write();
+
+ Vector3 sideways;
+ Vector3 up;
+ Vector3 forward;
+
+ Vector3 prev_sideways = Vector3(1, 0, 0);
+ Vector3 prev_up = Vector3(0, 1, 0);
+ Vector3 prev_forward = Vector3(0, 0, 1);
+
for (List<Plane>::Element *E = pointlist.front(); E; E = E->next()) {
w[idx] = E->get().normal;
wt[idx] = E->get().d;
+
+ if (!up_vector_enabled) {
+ idx++;
+ continue;
+ }
+
+ forward = idx > 0 ? (w[idx] - w[idx - 1]).normalized() : prev_forward;
+
+ float y_dot = prev_up.dot(forward);
+
+ if (y_dot > (1.0f - CMP_EPSILON)) {
+ sideways = prev_sideways;
+ up = -prev_forward;
+ } else if (y_dot < -(1.0f - CMP_EPSILON)) {
+ sideways = prev_sideways;
+ up = prev_forward;
+ } else {
+ sideways = prev_up.cross(forward).normalized();
+ up = forward.cross(sideways).normalized();
+ }
+
+ if (idx == 1)
+ up_write[0] = up;
+
+ up_write[idx] = up;
+
+ prev_sideways = sideways;
+ prev_up = up;
+ prev_forward = forward;
+
idx++;
}
}
@@ -1260,6 +1393,53 @@ float Curve3D::interpolate_baked_tilt(float p_offset) const {
return Math::lerp(r[idx], r[idx + 1], frac);
}
+Vector3 Curve3D::interpolate_baked_up_vector(float p_offset, bool p_apply_tilt) const {
+
+ if (baked_cache_dirty)
+ _bake();
+
+ //validate//
+ // curve may not have baked up vectors
+ int count = baked_up_vector_cache.size();
+ if (count == 0) {
+ ERR_EXPLAIN("No up vectors in Curve3D");
+ ERR_FAIL_COND_V(count == 0, Vector3(0, 1, 0));
+ }
+
+ if (count == 1)
+ return baked_up_vector_cache.get(0);
+
+ PoolVector3Array::Read r = baked_up_vector_cache.read();
+ PoolVector3Array::Read rp = baked_point_cache.read();
+ PoolRealArray::Read rt = baked_tilt_cache.read();
+
+ float offset = CLAMP(p_offset, 0.0f, baked_max_ofs);
+
+ int idx = Math::floor((double)offset / (double)bake_interval);
+ float frac = Math::fmod(offset, bake_interval) / bake_interval;
+
+ if (idx == count - 1)
+ return p_apply_tilt ? r[idx].rotated((rp[idx] - rp[idx - 1]).normalized(), rt[idx]) : r[idx];
+
+ Vector3 forward = (rp[idx + 1] - rp[idx]).normalized();
+ Vector3 up = r[idx];
+ Vector3 up1 = r[idx + 1];
+
+ if (p_apply_tilt) {
+ up.rotate(forward, rt[idx]);
+ up1.rotate(idx + 2 >= count ? forward : (rp[idx + 2] - rp[idx + 1]).normalized(), rt[idx + 1]);
+ }
+
+ Vector3 axis = up.cross(up1);
+
+ if (axis.length_squared() < CMP_EPSILON2)
+ axis = forward;
+ else
+ axis.normalize();
+
+ return up.rotated(axis, up.angle_to(up1) * frac);
+}
+
PoolVector3Array Curve3D::get_baked_points() const {
if (baked_cache_dirty)
@@ -1276,6 +1456,95 @@ PoolRealArray Curve3D::get_baked_tilts() const {
return baked_tilt_cache;
}
+PoolVector3Array Curve3D::get_baked_up_vectors() const {
+
+ if (baked_cache_dirty)
+ _bake();
+
+ return baked_up_vector_cache;
+}
+
+Vector3 Curve3D::get_closest_point(const Vector3 &p_to_point) const {
+ // Brute force method
+
+ if (baked_cache_dirty)
+ _bake();
+
+ //validate//
+ int pc = baked_point_cache.size();
+ if (pc == 0) {
+ ERR_EXPLAIN("No points in Curve3D");
+ ERR_FAIL_COND_V(pc == 0, Vector3());
+ }
+
+ if (pc == 1)
+ return baked_point_cache.get(0);
+
+ PoolVector3Array::Read r = baked_point_cache.read();
+
+ Vector3 nearest;
+ float nearest_dist = -1.0f;
+
+ for (int i = 0; i < pc - 1; i++) {
+ Vector3 origin = r[i];
+ Vector3 direction = (r[i + 1] - origin) / bake_interval;
+
+ float d = CLAMP((p_to_point - origin).dot(direction), 0.0f, bake_interval);
+ Vector3 proj = origin + direction * d;
+
+ float dist = proj.distance_squared_to(p_to_point);
+
+ if (nearest_dist < 0.0f || dist < nearest_dist) {
+ nearest = proj;
+ nearest_dist = dist;
+ }
+ }
+
+ return nearest;
+}
+
+float Curve3D::get_closest_offset(const Vector3 &p_to_point) const {
+ // Brute force method
+
+ if (baked_cache_dirty)
+ _bake();
+
+ //validate//
+ int pc = baked_point_cache.size();
+ if (pc == 0) {
+ ERR_EXPLAIN("No points in Curve3D");
+ ERR_FAIL_COND_V(pc == 0, 0.0f);
+ }
+
+ if (pc == 1)
+ return 0.0f;
+
+ PoolVector3Array::Read r = baked_point_cache.read();
+
+ float nearest = 0.0f;
+ float nearest_dist = -1.0f;
+ float offset = 0.0f;
+
+ for (int i = 0; i < pc - 1; i++) {
+ Vector3 origin = r[i];
+ Vector3 direction = (r[i + 1] - origin) / bake_interval;
+
+ float d = CLAMP((p_to_point - origin).dot(direction), 0.0f, bake_interval);
+ Vector3 proj = origin + direction * d;
+
+ float dist = proj.distance_squared_to(p_to_point);
+
+ if (nearest_dist < 0.0f || dist < nearest_dist) {
+ nearest = offset + d;
+ nearest_dist = dist;
+ }
+
+ offset += bake_interval;
+ }
+
+ return nearest;
+}
+
void Curve3D::set_bake_interval(float p_tolerance) {
bake_interval = p_tolerance;
@@ -1288,6 +1557,18 @@ float Curve3D::get_bake_interval() const {
return bake_interval;
}
+void Curve3D::set_up_vector_enabled(bool p_enable) {
+
+ up_vector_enabled = p_enable;
+ baked_cache_dirty = true;
+ emit_signal(CoreStringNames::get_singleton()->changed);
+}
+
+bool Curve3D::is_up_vector_enabled() const {
+
+ return up_vector_enabled;
+}
+
Dictionary Curve3D::_get_data() const {
Dictionary dc;
@@ -1399,11 +1680,17 @@ void Curve3D::_bind_methods() {
//ClassDB::bind_method(D_METHOD("bake","subdivs"),&Curve3D::bake,DEFVAL(10));
ClassDB::bind_method(D_METHOD("set_bake_interval", "distance"), &Curve3D::set_bake_interval);
ClassDB::bind_method(D_METHOD("get_bake_interval"), &Curve3D::get_bake_interval);
+ ClassDB::bind_method(D_METHOD("set_up_vector_enabled", "enable"), &Curve3D::set_up_vector_enabled);
+ ClassDB::bind_method(D_METHOD("is_up_vector_enabled"), &Curve3D::is_up_vector_enabled);
ClassDB::bind_method(D_METHOD("get_baked_length"), &Curve3D::get_baked_length);
ClassDB::bind_method(D_METHOD("interpolate_baked", "offset", "cubic"), &Curve3D::interpolate_baked, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("interpolate_baked_up_vector", "offset", "apply_tilt"), &Curve3D::interpolate_baked_up_vector, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_baked_points"), &Curve3D::get_baked_points);
ClassDB::bind_method(D_METHOD("get_baked_tilts"), &Curve3D::get_baked_tilts);
+ ClassDB::bind_method(D_METHOD("get_baked_up_vectors"), &Curve3D::get_baked_up_vectors);
+ ClassDB::bind_method(D_METHOD("get_closest_point", "to_point"), &Curve3D::get_closest_point);
+ ClassDB::bind_method(D_METHOD("get_closest_offset", "to_point"), &Curve3D::get_closest_offset);
ClassDB::bind_method(D_METHOD("tessellate", "max_stages", "tolerance_degrees"), &Curve3D::tessellate, DEFVAL(5), DEFVAL(4));
ClassDB::bind_method(D_METHOD("_get_data"), &Curve3D::_get_data);
@@ -1411,6 +1698,9 @@ void Curve3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::REAL, "bake_interval", PROPERTY_HINT_RANGE, "0.01,512,0.01"), "set_bake_interval", "get_bake_interval");
ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
+
+ ADD_GROUP("Up Vector", "up_vector_");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "up_vector_enabled"), "set_up_vector_enabled", "is_up_vector_enabled");
}
Curve3D::Curve3D() {
@@ -1420,4 +1710,5 @@ Curve3D::Curve3D() {
add_point(Vector3(0,2,0));
add_point(Vector3(0,3,5));*/
bake_interval = 0.2;
+ up_vector_enabled = true;
}
diff --git a/scene/resources/curve.h b/scene/resources/curve.h
index 4f55e4055c..9cb12a4345 100644
--- a/scene/resources/curve.h
+++ b/scene/resources/curve.h
@@ -199,6 +199,8 @@ public:
float get_baked_length() const;
Vector2 interpolate_baked(float p_offset, bool p_cubic = false) const;
PoolVector2Array get_baked_points() const; //useful for going through
+ Vector2 get_closest_point(const Vector2 &p_to_point) const;
+ float get_closest_offset(const Vector2 &p_to_point) const;
PoolVector2Array tessellate(int p_max_stages = 5, float p_tolerance = 4) const; //useful for display
@@ -230,11 +232,13 @@ class Curve3D : public Resource {
mutable bool baked_cache_dirty;
mutable PoolVector3Array baked_point_cache;
mutable PoolRealArray baked_tilt_cache;
+ mutable PoolVector3Array baked_up_vector_cache;
mutable float baked_max_ofs;
void _bake() const;
float bake_interval;
+ bool up_vector_enabled;
void _bake_segment3d(Map<float, Vector3> &r_bake, float p_begin, float p_end, const Vector3 &p_a, const Vector3 &p_out, const Vector3 &p_b, const Vector3 &p_in, int p_depth, int p_max_depth, float p_tol) const;
Dictionary _get_data() const;
@@ -262,12 +266,18 @@ public:
void set_bake_interval(float p_tolerance);
float get_bake_interval() const;
+ void set_up_vector_enabled(bool p_enable);
+ bool is_up_vector_enabled() const;
float get_baked_length() const;
Vector3 interpolate_baked(float p_offset, bool p_cubic = false) const;
float interpolate_baked_tilt(float p_offset) const;
+ Vector3 interpolate_baked_up_vector(float p_offset, bool p_apply_tilt = false) const;
PoolVector3Array get_baked_points() const; //useful for going through
PoolRealArray get_baked_tilts() const; //useful for going through
+ PoolVector3Array get_baked_up_vectors() const;
+ Vector3 get_closest_point(const Vector3 &p_to_point) const;
+ float get_closest_offset(const Vector3 &p_to_point) const;
PoolVector3Array tessellate(int p_max_stages = 5, float p_tolerance = 4) const; //useful for display
diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp
index ea70797530..3ea856541e 100644
--- a/scene/resources/default_theme/default_theme.cpp
+++ b/scene/resources/default_theme/default_theme.cpp
@@ -415,6 +415,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("font_color", "Label", Color(1, 1, 1));
theme->set_color("font_color_shadow", "Label", Color(0, 0, 0, 0));
+ theme->set_color("font_outline_modulate", "Label", Color(1, 1, 1));
theme->set_constant("shadow_offset_x", "Label", 1 * scale);
theme->set_constant("shadow_offset_y", "Label", 1 * scale);
@@ -543,6 +544,11 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_icon("updown", "SpinBox", make_icon(spinbox_updown_png));
+ //scroll container
+ Ref<StyleBoxEmpty> empty;
+ empty.instance();
+ theme->set_stylebox("bg", "ScrollContainer", empty);
+
// WindowDialog
theme->set_stylebox("panel", "WindowDialog", sb_expand(make_stylebox(popup_window_png, 10, 26, 10, 8), 8, 24, 8, 6));
@@ -818,6 +824,12 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("font_color_selected", "RichTextLabel", font_color_selection);
theme->set_color("selection_color", "RichTextLabel", Color(0.1, 0.1, 1, 0.8));
+ theme->set_color("font_color_shadow", "RichTextLabel", Color(0, 0, 0, 0));
+
+ theme->set_constant("shadow_offset_x", "RichTextLabel", 1 * scale);
+ theme->set_constant("shadow_offset_y", "RichTextLabel", 1 * scale);
+ theme->set_constant("shadow_as_outline", "RichTextLabel", 0 * scale);
+
theme->set_constant("line_separation", "RichTextLabel", 1 * scale);
theme->set_constant("table_hseparation", "RichTextLabel", 3 * scale);
theme->set_constant("table_vseparation", "RichTextLabel", 3 * scale);
diff --git a/scene/resources/dynamic_font.cpp b/scene/resources/dynamic_font.cpp
index 26e29b3ccb..05493d5777 100644
--- a/scene/resources/dynamic_font.cpp
+++ b/scene/resources/dynamic_font.cpp
@@ -33,8 +33,12 @@
#include "os/file_access.h"
#include "os/os.h"
-bool DynamicFontData::CacheID::operator<(CacheID right) const {
+#include FT_STROKER_H
+
+#define __STDC_LIMIT_MACROS
+#include <stdint.h>
+bool DynamicFontData::CacheID::operator<(CacheID right) const {
return key < right.key;
}
@@ -212,8 +216,8 @@ Error DynamicFontAtSize::_load() {
error = FT_Set_Pixel_Sizes(face, 0, id.size * oversampling);
}
- ascent = (face->size->metrics.ascender >> 6) / oversampling * scale_color_font;
- descent = (-face->size->metrics.descender >> 6) / oversampling * scale_color_font;
+ ascent = (face->size->metrics.ascender / 64.0) / oversampling * scale_color_font;
+ descent = (-face->size->metrics.descender / 64.0) / oversampling * scale_color_font;
linegap = 0;
texture_flags = 0;
if (id.mipmaps)
@@ -241,18 +245,11 @@ float DynamicFontAtSize::get_descent() const {
return descent;
}
-Size2 DynamicFontAtSize::get_char_size(CharType p_char, CharType p_next, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const {
+const Pair<const DynamicFontAtSize::Character *, DynamicFontAtSize *> DynamicFontAtSize::_find_char_with_font(CharType p_char, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const {
+ const Character *chr = char_map.getptr(p_char);
+ ERR_FAIL_COND_V(!chr, (Pair<const Character *, DynamicFontAtSize *>(NULL, NULL)));
- if (!valid)
- return Size2(1, 1);
- const_cast<DynamicFontAtSize *>(this)->_update_char(p_char);
-
- const Character *c = char_map.getptr(p_char);
- ERR_FAIL_COND_V(!c, Size2());
-
- Size2 ret(0, get_height());
-
- if (!c->found) {
+ if (!chr->found) {
//not found, try in fallbacks
for (int i = 0; i < p_fallbacks.size(); i++) {
@@ -262,52 +259,54 @@ Size2 DynamicFontAtSize::get_char_size(CharType p_char, CharType p_next, const V
continue;
fb->_update_char(p_char);
- const Character *ch = fb->char_map.getptr(p_char);
- ERR_CONTINUE(!ch);
+ const Character *fallback_chr = fb->char_map.getptr(p_char);
+ ERR_CONTINUE(!fallback_chr);
- if (!ch->found)
+ if (!fallback_chr->found)
continue;
- c = ch;
- break;
+ return Pair<const Character *, DynamicFontAtSize *>(fallback_chr, fb);
}
- //not found, try 0xFFFD to display 'not found'.
- if (!c->found) {
-
- const_cast<DynamicFontAtSize *>(this)->_update_char(0xFFFD);
- c = char_map.getptr(0xFFFD);
- ERR_FAIL_COND_V(!c, Size2());
- }
+ //not found, try 0xFFFD to display 'not found'.
+ const_cast<DynamicFontAtSize *>(this)->_update_char(0xFFFD);
+ chr = char_map.getptr(0xFFFD);
+ ERR_FAIL_COND_V(!chr, (Pair<const Character *, DynamicFontAtSize *>(NULL, NULL)));
}
- if (c->found) {
- ret.x = c->advance;
- }
+ return Pair<const Character *, DynamicFontAtSize *>(chr, const_cast<DynamicFontAtSize *>(this));
+}
+
+float DynamicFontAtSize::_get_kerning_advance(const DynamicFontAtSize *font, CharType p_char, CharType p_next) const {
+ float advance = 0.0;
if (p_next) {
FT_Vector delta;
- FT_Get_Kerning(face, p_char, p_next, FT_KERNING_DEFAULT, &delta);
+ FT_Get_Kerning(font->face, p_char, p_next, FT_KERNING_DEFAULT, &delta);
+ advance = (delta.x / 64.0) / oversampling;
+ }
+
+ return advance;
+}
- if (delta.x == 0) {
- for (int i = 0; i < p_fallbacks.size(); i++) {
+Size2 DynamicFontAtSize::get_char_size(CharType p_char, CharType p_next, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const {
- DynamicFontAtSize *fb = const_cast<DynamicFontAtSize *>(p_fallbacks[i].ptr());
- if (!fb->valid)
- continue;
+ if (!valid)
+ return Size2(1, 1);
+ const_cast<DynamicFontAtSize *>(this)->_update_char(p_char);
- FT_Get_Kerning(fb->face, p_char, p_next, FT_KERNING_DEFAULT, &delta);
+ Pair<const Character *, DynamicFontAtSize *> char_pair_with_font = _find_char_with_font(p_char, p_fallbacks);
+ const Character *ch = char_pair_with_font.first;
+ DynamicFontAtSize *font = char_pair_with_font.second;
+ ERR_FAIL_COND_V(!ch, Size2());
- if (delta.x == 0)
- continue;
+ Size2 ret(0, get_height());
- ret.x += (delta.x >> 6) / oversampling;
- break;
- }
- } else {
- ret.x += (delta.x >> 6) / oversampling;
- }
+ if (ch->found) {
+ ret.x = ch->advance;
}
+ ret.x += _get_kerning_advance(font, p_char, p_next);
+
return ret;
}
@@ -321,102 +320,42 @@ void DynamicFontAtSize::set_texture_flags(uint32_t p_flags) {
}
}
-float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const {
+float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks, bool p_advance_only) const {
if (!valid)
return 0;
const_cast<DynamicFontAtSize *>(this)->_update_char(p_char);
- const Character *c = char_map.getptr(p_char);
+ Pair<const Character *, DynamicFontAtSize *> char_pair_with_font = _find_char_with_font(p_char, p_fallbacks);
+ const Character *ch = char_pair_with_font.first;
+ DynamicFontAtSize *font = char_pair_with_font.second;
- float advance = 0;
+ ERR_FAIL_COND_V(!ch, 0.0);
- if (!c->found) {
+ float advance = 0.0;
- //not found, try in fallbacks
- bool used_fallback = false;
-
- for (int i = 0; i < p_fallbacks.size(); i++) {
+ if (ch->found) {
+ ERR_FAIL_COND_V(ch->texture_idx < -1 || ch->texture_idx >= font->textures.size(), 0);
- DynamicFontAtSize *fb = const_cast<DynamicFontAtSize *>(p_fallbacks[i].ptr());
- if (!fb->valid)
- continue;
-
- fb->_update_char(p_char);
- const Character *ch = fb->char_map.getptr(p_char);
- ERR_CONTINUE(!ch);
-
- if (!ch->found)
- continue;
+ if (!p_advance_only && ch->texture_idx != -1) {
Point2 cpos = p_pos;
cpos.x += ch->h_align;
- cpos.y -= fb->get_ascent();
+ cpos.y -= font->get_ascent();
cpos.y += ch->v_align;
- ERR_FAIL_COND_V(ch->texture_idx < -1 || ch->texture_idx >= fb->textures.size(), 0);
- if (ch->texture_idx != -1) {
- Color modulate = p_modulate;
- if (FT_HAS_COLOR(fb->face)) {
- modulate.r = modulate.g = modulate.b = 1;
- }
- VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, ch->rect.size * Vector2(fb->scale_color_font, fb->scale_color_font)), fb->textures[ch->texture_idx].texture->get_rid(), ch->rect_uv, modulate, false, RID(), false);
- }
- advance = ch->advance;
- used_fallback = true;
- break;
- }
- //not found, try 0xFFFD to display 'not found'.
-
- if (!used_fallback) {
-
- const_cast<DynamicFontAtSize *>(this)->_update_char(0xFFFD);
- c = char_map.getptr(0xFFFD);
- }
- }
-
- if (c->found) {
-
- Point2 cpos = p_pos;
- cpos.x += c->h_align;
- cpos.y -= get_ascent();
- cpos.y += c->v_align;
- ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), 0);
- if (c->texture_idx != -1) {
Color modulate = p_modulate;
if (FT_HAS_COLOR(face)) {
- modulate.r = modulate.g = modulate.b = 1;
+ modulate.r = modulate.g = modulate.b = 1.0;
}
- VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, c->rect.size * Vector2(scale_color_font, scale_color_font)), textures[c->texture_idx].texture->get_rid(), c->rect_uv, modulate, false, RID(), false);
+ RID texture = font->textures[ch->texture_idx].texture->get_rid();
+ VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, ch->rect.size * Vector2(font->scale_color_font, font->scale_color_font)), texture, ch->rect_uv, modulate, false, RID(), false);
}
- advance = c->advance;
- //textures[c->texture_idx].texture->draw(p_canvas_item,Vector2());
- }
- if (p_next) {
-
- FT_Vector delta;
- FT_Get_Kerning(face, p_char, p_next, FT_KERNING_DEFAULT, &delta);
-
- if (delta.x == 0) {
- for (int i = 0; i < p_fallbacks.size(); i++) {
-
- DynamicFontAtSize *fb = const_cast<DynamicFontAtSize *>(p_fallbacks[i].ptr());
- if (!fb->valid)
- continue;
-
- FT_Get_Kerning(fb->face, p_char, p_next, FT_KERNING_DEFAULT, &delta);
-
- if (delta.x == 0)
- continue;
-
- advance += (delta.x >> 6) / oversampling;
- break;
- }
- } else {
- advance += (delta.x >> 6) / oversampling;
- }
+ advance = ch->advance;
}
+ advance += _get_kerning_advance(font, p_char, p_next);
+
return advance;
}
@@ -440,96 +379,37 @@ void DynamicFontAtSize::_ft_stream_close(FT_Stream stream) {
memdelete(f);
}
-void DynamicFontAtSize::_update_char(CharType p_char) {
-
- if (char_map.has(p_char))
- return;
-
- _THREAD_SAFE_METHOD_
-
- FT_GlyphSlot slot = face->glyph;
-
- if (FT_Get_Char_Index(face, p_char) == 0) {
- //not found
- Character ch;
- ch.texture_idx = -1;
- ch.advance = 0;
- ch.h_align = 0;
- ch.v_align = 0;
- ch.found = false;
-
- char_map[p_char] = ch;
- return;
- }
-
- int ft_hinting;
-
- switch (font->hinting) {
- case DynamicFontData::HINTING_NONE:
- ft_hinting = FT_LOAD_NO_HINTING;
- break;
- case DynamicFontData::HINTING_LIGHT:
- ft_hinting = FT_LOAD_TARGET_LIGHT;
- break;
- default:
- ft_hinting = FT_LOAD_TARGET_NORMAL;
- break;
- }
-
- int error = FT_Load_Char(face, p_char, FT_HAS_COLOR(face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (font->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0) | ft_hinting);
- if (!error) {
- error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
- }
- if (error) {
-
- int advance = 0;
- Character ch;
- ch.texture_idx = -1;
- ch.advance = advance;
- ch.h_align = 0;
- ch.v_align = 0;
- ch.found = false;
-
- char_map[p_char] = ch;
-
- return;
- }
-
- int w = slot->bitmap.width;
- int h = slot->bitmap.rows;
- int yofs = slot->bitmap_top;
- int xofs = slot->bitmap_left;
- int advance = slot->advance.x >> 6;
-
- int mw = w + rect_margin * 2;
- int mh = h + rect_margin * 2;
-
- if (mw > 4096 || mh > 4096) {
-
- ERR_FAIL_COND(mw > 4096);
- ERR_FAIL_COND(mh > 4096);
- }
+DynamicFontAtSize::Character DynamicFontAtSize::Character::not_found() {
+ Character ch;
+ ch.texture_idx = -1;
+ ch.advance = 0;
+ ch.h_align = 0;
+ ch.v_align = 0;
+ ch.found = false;
+ return ch;
+}
- //find a texture to fit this...
+DynamicFontAtSize::TexturePosition DynamicFontAtSize::_find_texture_pos_for_glyph(int p_color_size, Image::Format p_image_format, int p_width, int p_height) {
+ TexturePosition ret;
+ ret.index = -1;
+ ret.x = 0;
+ ret.y = 0;
- int color_size = slot->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? 4 : 2;
- Image::Format require_format = color_size == 4 ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8;
- int tex_index = -1;
- int tex_x = 0;
- int tex_y = 0;
+ int mw = p_width;
+ int mh = p_height;
for (int i = 0; i < textures.size(); i++) {
CharTexture &ct = textures[i];
- if (ct.texture->get_format() != require_format)
+ if (ct.texture->get_format() != p_image_format)
continue;
if (mw > ct.texture_size || mh > ct.texture_size) //too big for this texture
continue;
- tex_y = 0x7FFFFFFF;
- tex_x = 0;
+ ret.y = 0x7FFFFFFF;
+ ret.x = 0;
for (int j = 0; j < ct.texture_size - mw; j++) {
@@ -542,25 +422,27 @@ void DynamicFontAtSize::_update_char(CharType p_char) {
max_y = y;
}
- if (max_y < tex_y) {
- tex_y = max_y;
- tex_x = j;
+ if (max_y < ret.y) {
+ ret.y = max_y;
+ ret.x = j;
}
}
- if (tex_y == 0x7FFFFFFF || tex_y + mh > ct.texture_size)
+ if (ret.y == 0x7FFFFFFF || ret.y + mh > ct.texture_size)
continue; //fail, could not fit it here
- tex_index = i;
+ ret.index = i;
break;
}
- if (tex_index == -1) {
+ //print_line("CHAR: "+String::chr(p_char)+" TEX INDEX: "+itos(tex_index)+" X: "+itos(tex_x)+" Y: "+itos(tex_y));
+
+ if (ret.index == -1) {
//could not find texture to fit, create one
- tex_x = 0;
- tex_y = 0;
+ ret.x = 0;
+ ret.y = 0;
- int texsize = MAX(id.size * 8, 256);
+ int texsize = MAX(id.size * oversampling * 8, 256);
if (mw > texsize)
texsize = mw; //special case, adapt to it?
if (mh > texsize)
@@ -572,13 +454,13 @@ void DynamicFontAtSize::_update_char(CharType p_char) {
CharTexture tex;
tex.texture_size = texsize;
- tex.imgdata.resize(texsize * texsize * color_size);
+ tex.imgdata.resize(texsize * texsize * p_color_size); //grayscale alpha
{
//zero texture
PoolVector<uint8_t>::Write w = tex.imgdata.write();
- ERR_FAIL_COND(texsize * texsize * color_size > tex.imgdata.size());
- for (int i = 0; i < texsize * texsize * color_size; i++) {
+ ERR_FAIL_COND_V(texsize * texsize * p_color_size > tex.imgdata.size(), ret);
+ for (int i = 0; i < texsize * texsize * p_color_size; i++) {
w[i] = 0;
}
}
@@ -587,12 +469,31 @@ void DynamicFontAtSize::_update_char(CharType p_char) {
tex.offsets[i] = 0;
textures.push_back(tex);
- tex_index = textures.size() - 1;
+ ret.index = textures.size() - 1;
}
+ return ret;
+}
+
+DynamicFontAtSize::Character DynamicFontAtSize::_bitmap_to_character(FT_Bitmap bitmap, int yofs, int xofs, float advance) {
+ int w = bitmap.width;
+ int h = bitmap.rows;
+
+ int mw = w + rect_margin * 2;
+ int mh = h + rect_margin * 2;
+
+ ERR_FAIL_COND_V(mw > 4096, Character::not_found());
+ ERR_FAIL_COND_V(mh > 4096, Character::not_found());
+
+ int color_size = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? 4 : 2;
+ Image::Format require_format = color_size == 4 ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8;
+
+ TexturePosition tex_pos = _find_texture_pos_for_glyph(color_size, require_format, mw, mh);
+ ERR_FAIL_COND_V(tex_pos.index < 0, Character::not_found());
+
//fit character in char texture
- CharTexture &tex = textures[tex_index];
+ CharTexture &tex = textures[tex_pos.index];
{
PoolVector<uint8_t>::Write wr = tex.imgdata.write();
@@ -600,30 +501,30 @@ void DynamicFontAtSize::_update_char(CharType p_char) {
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
- int ofs = ((i + tex_y + rect_margin) * tex.texture_size + j + tex_x + rect_margin) * color_size;
- ERR_FAIL_COND(ofs >= tex.imgdata.size());
- switch (slot->bitmap.pixel_mode) {
+ int ofs = ((i + tex_pos.y + rect_margin) * tex.texture_size + j + tex_pos.x + rect_margin) * color_size;
+ ERR_FAIL_COND_V(ofs >= tex.imgdata.size(), Character::not_found());
+ switch (bitmap.pixel_mode) {
case FT_PIXEL_MODE_MONO: {
- int byte = i * slot->bitmap.pitch + (j >> 3);
+ int byte = i * bitmap.pitch + (j >> 3);
int bit = 1 << (7 - (j % 8));
wr[ofs + 0] = 255; //grayscale as 1
- wr[ofs + 1] = slot->bitmap.buffer[byte] & bit ? 255 : 0;
+ wr[ofs + 1] = bitmap.buffer[byte] & bit ? 255 : 0;
} break;
case FT_PIXEL_MODE_GRAY:
wr[ofs + 0] = 255; //grayscale as 1
- wr[ofs + 1] = slot->bitmap.buffer[i * slot->bitmap.pitch + j];
+ wr[ofs + 1] = bitmap.buffer[i * bitmap.pitch + j];
break;
case FT_PIXEL_MODE_BGRA: {
- int ofs_color = i * slot->bitmap.pitch + (j << 2);
- wr[ofs + 2] = slot->bitmap.buffer[ofs_color + 0];
- wr[ofs + 1] = slot->bitmap.buffer[ofs_color + 1];
- wr[ofs + 0] = slot->bitmap.buffer[ofs_color + 2];
- wr[ofs + 3] = slot->bitmap.buffer[ofs_color + 3];
+ int ofs_color = i * bitmap.pitch + (j << 2);
+ wr[ofs + 2] = bitmap.buffer[ofs_color + 0];
+ wr[ofs + 1] = bitmap.buffer[ofs_color + 1];
+ wr[ofs + 0] = bitmap.buffer[ofs_color + 2];
+ wr[ofs + 3] = bitmap.buffer[ofs_color + 3];
} break;
// TODO: FT_PIXEL_MODE_LCD
default:
- ERR_EXPLAIN("Font uses unsupported pixel format: " + itos(slot->bitmap.pixel_mode));
- ERR_FAIL();
+ ERR_EXPLAIN("Font uses unsupported pixel format: " + itos(bitmap.pixel_mode));
+ ERR_FAIL_V(Character::not_found());
break;
}
}
@@ -645,31 +546,105 @@ void DynamicFontAtSize::_update_char(CharType p_char) {
// update height array
- for (int k = tex_x; k < tex_x + mw; k++) {
-
- tex.offsets[k] = tex_y + mh;
+ for (int k = tex_pos.x; k < tex_pos.x + mw; k++) {
+ tex.offsets[k] = tex_pos.y + mh;
}
Character chr;
chr.h_align = xofs * scale_color_font / oversampling;
chr.v_align = ascent - (yofs * scale_color_font / oversampling); // + ascent - descent;
chr.advance = advance * scale_color_font / oversampling;
- chr.texture_idx = tex_index;
+ chr.texture_idx = tex_pos.index;
chr.found = true;
- chr.rect_uv = Rect2(tex_x + rect_margin, tex_y + rect_margin, w, h);
+ chr.rect_uv = Rect2(tex_pos.x + rect_margin, tex_pos.y + rect_margin, w, h);
chr.rect = chr.rect_uv;
chr.rect.position /= oversampling;
- chr.rect.size /= oversampling;
+ chr.rect.size = chr.rect.size * scale_color_font / oversampling;
+ return chr;
+}
+
+DynamicFontAtSize::Character DynamicFontAtSize::_make_outline_char(CharType p_char) {
+ Character ret = Character::not_found();
- char_map[p_char] = chr;
+ if (FT_Load_Char(face, p_char, FT_LOAD_NO_BITMAP | (font->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)) != 0)
+ return ret;
+
+ FT_Stroker stroker;
+ if (FT_Stroker_New(library, &stroker) != 0)
+ return ret;
+
+ FT_Stroker_Set(stroker, (int)(id.outline_size * oversampling * 64.0), FT_STROKER_LINECAP_BUTT, FT_STROKER_LINEJOIN_ROUND, 0);
+ FT_Glyph glyph;
+ FT_BitmapGlyph glyph_bitmap;
+
+ if (FT_Get_Glyph(face->glyph, &glyph) != 0)
+ goto cleanup_stroker;
+ if (FT_Glyph_Stroke(&glyph, stroker, 1) != 0)
+ goto cleanup_glyph;
+ if (FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1) != 0)
+ goto cleanup_glyph;
+
+ glyph_bitmap = (FT_BitmapGlyph)glyph;
+ ret = _bitmap_to_character(glyph_bitmap->bitmap, glyph_bitmap->top, glyph_bitmap->left, glyph->advance.x / 65536.0);
+
+cleanup_glyph:
+ FT_Done_Glyph(glyph);
+cleanup_stroker:
+ FT_Stroker_Done(stroker);
+ return ret;
}
-bool DynamicFontAtSize::update_oversampling() {
- if (oversampling == font_oversampling)
- return false;
- if (!valid)
- return false;
+void DynamicFontAtSize::_update_char(CharType p_char) {
+
+ if (char_map.has(p_char))
+ return;
+
+ _THREAD_SAFE_METHOD_
+
+ Character character = Character::not_found();
+
+ FT_GlyphSlot slot = face->glyph;
+
+ if (FT_Get_Char_Index(face, p_char) == 0) {
+ char_map[p_char] = character;
+ return;
+ }
+
+ int ft_hinting;
+
+ switch (font->hinting) {
+ case DynamicFontData::HINTING_NONE:
+ ft_hinting = FT_LOAD_NO_HINTING;
+ break;
+ case DynamicFontData::HINTING_LIGHT:
+ ft_hinting = FT_LOAD_TARGET_LIGHT;
+ break;
+ default:
+ ft_hinting = FT_LOAD_TARGET_NORMAL;
+ break;
+ }
+
+ int error = FT_Load_Char(face, p_char, FT_HAS_COLOR(face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (font->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0));
+ if (error) {
+ char_map[p_char] = character;
+ return;
+ }
+
+ if (id.outline_size > 0) {
+ character = _make_outline_char(p_char);
+ } else {
+ error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
+ if (!error)
+ character = _bitmap_to_character(slot->bitmap, slot->bitmap_top, slot->bitmap_left, slot->advance.x / 64.0);
+ }
+
+ char_map[p_char] = character;
+}
+
+void DynamicFontAtSize::update_oversampling() {
+ if (oversampling == font_oversampling || !valid)
+ return;
FT_Done_FreeType(library);
textures.clear();
@@ -677,8 +652,6 @@ bool DynamicFontAtSize::update_oversampling() {
oversampling = font_oversampling;
valid = false;
_load();
-
- return true;
}
DynamicFontAtSize::DynamicFontAtSize() {
@@ -707,11 +680,27 @@ DynamicFontAtSize::~DynamicFontAtSize() {
void DynamicFont::_reload_cache() {
ERR_FAIL_COND(cache_id.size < 1);
- if (!data.is_valid())
+ if (!data.is_valid()) {
+ data_at_size.unref();
+ outline_data_at_size.unref();
+ fallback_data_at_size.resize(0);
+ fallback_outline_data_at_size.resize(0);
return;
+ }
+
data_at_size = data->_get_dynamic_font_at_size(cache_id);
+ if (outline_cache_id.outline_size > 0) {
+ outline_data_at_size = data->_get_dynamic_font_at_size(outline_cache_id);
+ fallback_outline_data_at_size.resize(fallback_data_at_size.size());
+ } else {
+ outline_data_at_size.unref();
+ fallback_outline_data_at_size.resize(0);
+ }
+
for (int i = 0; i < fallbacks.size(); i++) {
fallback_data_at_size[i] = fallbacks[i]->_get_dynamic_font_at_size(cache_id);
+ if (outline_cache_id.outline_size > 0)
+ fallback_outline_data_at_size[i] = fallbacks[i]->_get_dynamic_font_at_size(outline_cache_id);
}
emit_changed();
@@ -721,12 +710,10 @@ void DynamicFont::_reload_cache() {
void DynamicFont::set_font_data(const Ref<DynamicFontData> &p_data) {
data = p_data;
- if (data.is_valid())
- data_at_size = data->_get_dynamic_font_at_size(cache_id);
- else
- data_at_size = Ref<DynamicFontAtSize>();
+ _reload_cache();
emit_changed();
+ _change_notify();
}
Ref<DynamicFontData> DynamicFont::get_font_data() const {
@@ -739,6 +726,7 @@ void DynamicFont::set_size(int p_size) {
if (cache_id.size == p_size)
return;
cache_id.size = p_size;
+ outline_cache_id.size = p_size;
_reload_cache();
}
@@ -747,6 +735,30 @@ int DynamicFont::get_size() const {
return cache_id.size;
}
+void DynamicFont::set_outline_size(int p_size) {
+ if (outline_cache_id.outline_size == p_size)
+ return;
+ ERR_FAIL_COND(p_size < 0 || p_size > UINT8_MAX);
+ outline_cache_id.outline_size = p_size;
+ _reload_cache();
+}
+
+int DynamicFont::get_outline_size() const {
+ return outline_cache_id.outline_size;
+}
+
+void DynamicFont::set_outline_color(Color p_color) {
+ if (p_color != outline_color) {
+ outline_color = p_color;
+ emit_changed();
+ _change_notify();
+ }
+}
+
+Color DynamicFont::get_outline_color() const {
+ return outline_color;
+}
+
bool DynamicFont::get_use_mipmaps() const {
return cache_id.mipmaps;
@@ -757,6 +769,7 @@ void DynamicFont::set_use_mipmaps(bool p_enable) {
if (cache_id.mipmaps == p_enable)
return;
cache_id.mipmaps = p_enable;
+ outline_cache_id.mipmaps = p_enable;
_reload_cache();
}
@@ -770,6 +783,7 @@ void DynamicFont::set_use_filter(bool p_enable) {
if (cache_id.filter == p_enable)
return;
cache_id.filter = p_enable;
+ outline_cache_id.filter = p_enable;
_reload_cache();
}
@@ -859,13 +873,24 @@ bool DynamicFont::is_distance_field_hint() const {
return false;
}
-float DynamicFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate) const {
+bool DynamicFont::has_outline() const {
+ return outline_cache_id.outline_size > 0;
+}
+
+float DynamicFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, bool p_outline) const {
+ const Ref<DynamicFontAtSize> &font_at_size = p_outline && outline_cache_id.outline_size > 0 ? outline_data_at_size : data_at_size;
- if (!data_at_size.is_valid())
+ if (!font_at_size.is_valid())
return 0;
- return data_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate, fallback_data_at_size) + spacing_char;
+ const Vector<Ref<DynamicFontAtSize> > &fallbacks = p_outline && outline_cache_id.outline_size > 0 ? fallback_outline_data_at_size : fallback_data_at_size;
+ Color color = p_outline && outline_cache_id.outline_size > 0 ? p_modulate * outline_color : p_modulate;
+
+ // If requested outline draw, but no outline is present, simply return advance without drawing anything
+ bool advance_only = p_outline && outline_cache_id.outline_size == 0;
+ return font_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, color, fallbacks, advance_only) + spacing_char;
}
+
void DynamicFont::set_fallback(int p_idx, const Ref<DynamicFontData> &p_data) {
ERR_FAIL_COND(p_data.is_null());
@@ -879,6 +904,8 @@ void DynamicFont::add_fallback(const Ref<DynamicFontData> &p_data) {
ERR_FAIL_COND(p_data.is_null());
fallbacks.push_back(p_data);
fallback_data_at_size.push_back(fallbacks[fallbacks.size() - 1]->_get_dynamic_font_at_size(cache_id)); //const..
+ if (outline_cache_id.outline_size > 0)
+ fallback_outline_data_at_size.push_back(fallbacks[fallbacks.size() - 1]->_get_dynamic_font_at_size(outline_cache_id));
_change_notify();
emit_changed();
@@ -963,6 +990,12 @@ void DynamicFont::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_size", "data"), &DynamicFont::set_size);
ClassDB::bind_method(D_METHOD("get_size"), &DynamicFont::get_size);
+ ClassDB::bind_method(D_METHOD("set_outline_size", "size"), &DynamicFont::set_outline_size);
+ ClassDB::bind_method(D_METHOD("get_outline_size"), &DynamicFont::get_outline_size);
+
+ ClassDB::bind_method(D_METHOD("set_outline_color", "color"), &DynamicFont::set_outline_color);
+ ClassDB::bind_method(D_METHOD("get_outline_color"), &DynamicFont::get_outline_color);
+
ClassDB::bind_method(D_METHOD("set_use_mipmaps", "enable"), &DynamicFont::set_use_mipmaps);
ClassDB::bind_method(D_METHOD("get_use_mipmaps"), &DynamicFont::get_use_mipmaps);
ClassDB::bind_method(D_METHOD("set_use_filter", "enable"), &DynamicFont::set_use_filter);
@@ -978,6 +1011,8 @@ void DynamicFont::_bind_methods() {
ADD_GROUP("Settings", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "size"), "set_size", "get_size");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "outline_size"), "set_outline_size", "get_outline_size");
+ ADD_PROPERTY(PropertyInfo(Variant::COLOR, "outline_color"), "set_outline_color", "get_outline_color");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_mipmaps"), "set_use_mipmaps", "get_use_mipmaps");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_filter"), "set_use_filter", "get_use_filter");
ADD_GROUP("Extra Spacing", "extra_spacing");
@@ -1005,6 +1040,7 @@ DynamicFont::DynamicFont() :
spacing_bottom = 0;
spacing_char = 0;
spacing_space = 0;
+ outline_color = Color(1, 1, 1);
if (dynamic_font_mutex)
dynamic_font_mutex->lock();
dynamic_fonts.add(&font_list);
@@ -1040,7 +1076,13 @@ void DynamicFont::update_oversampling() {
SelfList<DynamicFont> *E = dynamic_fonts.first();
while (E) {
- if (E->self()->data_at_size.is_valid() && E->self()->data_at_size->update_oversampling()) {
+ if (E->self()->data_at_size.is_valid()) {
+ E->self()->data_at_size->update_oversampling();
+
+ if (E->self()->outline_data_at_size.is_valid()) {
+ E->self()->outline_data_at_size->update_oversampling();
+ }
+
changed.push_back(Ref<DynamicFont>(E->self()));
}
E = E->next();
diff --git a/scene/resources/dynamic_font.h b/scene/resources/dynamic_font.h
index db8bd87587..f460bca2d4 100644
--- a/scene/resources/dynamic_font.h
+++ b/scene/resources/dynamic_font.h
@@ -35,6 +35,7 @@
#include "io/resource_loader.h"
#include "os/mutex.h"
#include "os/thread_safe.h"
+#include "pair.h"
#include "scene/resources/font.h"
#include <ft2build.h>
@@ -49,10 +50,10 @@ class DynamicFontData : public Resource {
public:
struct CacheID {
-
union {
struct {
uint32_t size : 16;
+ uint32_t outline_size : 8;
bool mipmaps : 1;
bool filter : 1;
};
@@ -148,8 +149,22 @@ class DynamicFontAtSize : public Reference {
texture_idx = 0;
v_align = 0;
}
+
+ static Character not_found();
};
+ struct TexturePosition {
+ int index;
+ int x;
+ int y;
+ };
+
+ const Pair<const Character *, DynamicFontAtSize *> _find_char_with_font(CharType p_char, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const;
+ Character _make_outline_char(CharType p_char);
+ float _get_kerning_advance(const DynamicFontAtSize *font, CharType p_char, CharType p_next) const;
+ TexturePosition _find_texture_pos_for_glyph(int p_color_size, Image::Format p_image_format, int p_width, int p_height);
+ Character _bitmap_to_character(FT_Bitmap bitmap, int yofs, int xofs, float advance);
+
static unsigned long _ft_stream_io(FT_Stream stream, unsigned long offset, unsigned char *buffer, unsigned long count);
static void _ft_stream_close(FT_Stream stream);
@@ -174,10 +189,10 @@ public:
Size2 get_char_size(CharType p_char, CharType p_next, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const;
- float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const;
+ float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks, bool p_advance_only = false) const;
void set_texture_flags(uint32_t p_flags);
- bool update_oversampling();
+ void update_oversampling();
DynamicFontAtSize();
~DynamicFontAtSize();
@@ -200,17 +215,23 @@ public:
private:
Ref<DynamicFontData> data;
Ref<DynamicFontAtSize> data_at_size;
+ Ref<DynamicFontAtSize> outline_data_at_size;
Vector<Ref<DynamicFontData> > fallbacks;
Vector<Ref<DynamicFontAtSize> > fallback_data_at_size;
+ Vector<Ref<DynamicFontAtSize> > fallback_outline_data_at_size;
DynamicFontData::CacheID cache_id;
+ DynamicFontData::CacheID outline_cache_id;
+
bool valid;
int spacing_top;
int spacing_bottom;
int spacing_char;
int spacing_space;
+ Color outline_color;
+
protected:
void _reload_cache();
@@ -227,6 +248,12 @@ public:
void set_size(int p_size);
int get_size() const;
+ void set_outline_size(int p_size);
+ int get_outline_size() const;
+
+ void set_outline_color(Color p_color);
+ Color get_outline_color() const;
+
bool get_use_mipmaps() const;
void set_use_mipmaps(bool p_enable);
@@ -251,7 +278,9 @@ public:
virtual bool is_distance_field_hint() const;
- virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) const;
+ virtual bool has_outline() const;
+
+ virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const;
SelfList<DynamicFont> font_list;
diff --git a/scene/resources/dynamic_font_stb.cpp b/scene/resources/dynamic_font_stb.cpp
index 098b794a95..29f1106d16 100644
--- a/scene/resources/dynamic_font_stb.cpp
+++ b/scene/resources/dynamic_font_stb.cpp
@@ -162,7 +162,7 @@ Size2 DynamicFontAtSize::get_char_size(CharType p_char, CharType p_next) const {
return ret;
}
-float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate) const {
+float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, bool p_outline) const {
const_cast<DynamicFontAtSize *>(this)->_update_char(p_char);
@@ -172,13 +172,15 @@ float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharT
return 0;
}
- Point2 cpos = p_pos;
- cpos.x += c->h_align;
- cpos.y -= get_ascent();
- cpos.y += c->v_align;
- ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), 0);
- if (c->texture_idx != -1)
- VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, c->rect.size), textures[c->texture_idx].texture->get_rid(), c->rect, p_modulate);
+ if (!p_outline) {
+ Point2 cpos = p_pos;
+ cpos.x += c->h_align;
+ cpos.y -= get_ascent();
+ cpos.y += c->v_align;
+ ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), 0);
+ if (c->texture_idx != -1)
+ VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, c->rect.size), textures[c->texture_idx].texture->get_rid(), c->rect, p_modulate);
+ }
//textures[c->texture_idx].texture->draw(p_canvas_item,Vector2());
@@ -459,12 +461,12 @@ bool DynamicFont::is_distance_field_hint() const {
return false;
}
-float DynamicFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate) const {
+float DynamicFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, bool p_outline) const {
if (!data_at_size.is_valid())
return 0;
- return data_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate);
+ return data_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate, p_outline);
}
DynamicFont::DynamicFont() {
diff --git a/scene/resources/dynamic_font_stb.h b/scene/resources/dynamic_font_stb.h
index 4c1097d28b..feae29c0c2 100644
--- a/scene/resources/dynamic_font_stb.h
+++ b/scene/resources/dynamic_font_stb.h
@@ -136,7 +136,7 @@ public:
Size2 get_char_size(CharType p_char, CharType p_next = 0) const;
- float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) const;
+ float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const;
DynamicFontAtSize();
~DynamicFontAtSize();
@@ -171,7 +171,7 @@ public:
virtual bool is_distance_field_hint() const;
- virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) const;
+ virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const;
DynamicFont();
~DynamicFont();
diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp
index 6fc5778dd8..0bae9d9b2d 100644
--- a/scene/resources/font.cpp
+++ b/scene/resources/font.cpp
@@ -32,12 +32,12 @@
#include "core/io/resource_loader.h"
#include "core/os/file_access.h"
+#include "method_bind_ext.gen.inc"
-void Font::draw_halign(RID p_canvas_item, const Point2 &p_pos, HAlign p_align, float p_width, const String &p_text, const Color &p_modulate) const {
-
+void Font::draw_halign(RID p_canvas_item, const Point2 &p_pos, HAlign p_align, float p_width, const String &p_text, const Color &p_modulate, const Color &p_outline_modulate) const {
float length = get_string_size(p_text).width;
if (length >= p_width) {
- draw(p_canvas_item, p_pos, p_text, p_modulate, p_width);
+ draw(p_canvas_item, p_pos, p_text, p_modulate, p_width, p_outline_modulate);
return;
}
@@ -56,13 +56,14 @@ void Font::draw_halign(RID p_canvas_item, const Point2 &p_pos, HAlign p_align, f
ERR_PRINT("Unknown halignment type");
} break;
}
- draw(p_canvas_item, p_pos + Point2(ofs, 0), p_text, p_modulate, p_width);
+ draw(p_canvas_item, p_pos + Point2(ofs, 0), p_text, p_modulate, p_width, p_outline_modulate);
}
-void Font::draw(RID p_canvas_item, const Point2 &p_pos, const String &p_text, const Color &p_modulate, int p_clip_w) const {
-
+void Font::draw(RID p_canvas_item, const Point2 &p_pos, const String &p_text, const Color &p_modulate, int p_clip_w, const Color &p_outline_modulate) const {
Vector2 ofs;
+ int chars_drawn = 0;
+ bool with_outline = has_outline();
for (int i = 0; i < p_text.length(); i++) {
int width = get_char_size(p_text[i]).width;
@@ -70,7 +71,15 @@ void Font::draw(RID p_canvas_item, const Point2 &p_pos, const String &p_text, co
if (p_clip_w >= 0 && (ofs.x + width) > p_clip_w)
break; //clip
- ofs.x += draw_char(p_canvas_item, p_pos + ofs, p_text[i], p_text[i + 1], p_modulate);
+ ofs.x += draw_char(p_canvas_item, p_pos + ofs, p_text[i], p_text[i + 1], with_outline ? p_outline_modulate : p_modulate, with_outline);
+ ++chars_drawn;
+ }
+
+ if (has_outline()) {
+ ofs = Vector2(0, 0);
+ for (int i = 0; i < chars_drawn; i++) {
+ ofs.x += draw_char(p_canvas_item, p_pos + ofs, p_text[i], p_text[i + 1], p_modulate, false);
+ }
}
}
@@ -81,13 +90,14 @@ void Font::update_changes() {
void Font::_bind_methods() {
- ClassDB::bind_method(D_METHOD("draw", "canvas_item", "position", "string", "modulate", "clip_w"), &Font::draw, DEFVAL(Color(1, 1, 1)), DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("draw", "canvas_item", "position", "string", "modulate", "clip_w", "outline_modulate"), &Font::draw, DEFVAL(Color(1, 1, 1)), DEFVAL(-1), DEFVAL(Color(1, 1, 1)));
ClassDB::bind_method(D_METHOD("get_ascent"), &Font::get_ascent);
ClassDB::bind_method(D_METHOD("get_descent"), &Font::get_descent);
ClassDB::bind_method(D_METHOD("get_height"), &Font::get_height);
ClassDB::bind_method(D_METHOD("is_distance_field_hint"), &Font::is_distance_field_hint);
ClassDB::bind_method(D_METHOD("get_string_size", "string"), &Font::get_string_size);
- ClassDB::bind_method(D_METHOD("draw_char", "canvas_item", "position", "char", "next", "modulate"), &Font::draw_char, DEFVAL(-1), DEFVAL(Color(1, 1, 1)));
+ ClassDB::bind_method(D_METHOD("has_outline"), &Font::has_outline);
+ ClassDB::bind_method(D_METHOD("draw_char", "canvas_item", "position", "char", "next", "modulate", "outline"), &Font::draw_char, DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(false));
ClassDB::bind_method(D_METHOD("update_changes"), &Font::update_changes);
}
@@ -494,23 +504,24 @@ Ref<BitmapFont> BitmapFont::get_fallback() const {
return fallback;
}
-float BitmapFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate) const {
+float BitmapFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, bool p_outline) const {
const Character *c = char_map.getptr(p_char);
if (!c) {
if (fallback.is_valid())
- return fallback->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate);
+ return fallback->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate, p_outline);
return 0;
}
- Point2 cpos = p_pos;
- cpos.x += c->h_align;
- cpos.y -= ascent;
- cpos.y += c->v_align;
ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), 0);
- if (c->texture_idx != -1)
+ if (!p_outline && c->texture_idx != -1) {
+ Point2 cpos = p_pos;
+ cpos.x += c->h_align;
+ cpos.y -= ascent;
+ cpos.y += c->v_align;
VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, c->rect.size), textures[c->texture_idx]->get_rid(), c->rect, p_modulate, false, RID(), false);
+ }
return get_char_size(p_char, p_next).width;
}
diff --git a/scene/resources/font.h b/scene/resources/font.h
index ae08890be3..4e295b6035 100644
--- a/scene/resources/font.h
+++ b/scene/resources/font.h
@@ -56,14 +56,55 @@ public:
virtual bool is_distance_field_hint() const = 0;
- void draw(RID p_canvas_item, const Point2 &p_pos, const String &p_text, const Color &p_modulate = Color(1, 1, 1), int p_clip_w = -1) const;
- void draw_halign(RID p_canvas_item, const Point2 &p_pos, HAlign p_align, float p_width, const String &p_text, const Color &p_modulate = Color(1, 1, 1)) const;
- virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) const = 0;
+ void draw(RID p_canvas_item, const Point2 &p_pos, const String &p_text, const Color &p_modulate = Color(1, 1, 1), int p_clip_w = -1, const Color &p_outline_modulate = Color(1, 1, 1)) const;
+ void draw_halign(RID p_canvas_item, const Point2 &p_pos, HAlign p_align, float p_width, const String &p_text, const Color &p_modulate = Color(1, 1, 1), const Color &p_outline_modulate = Color(1, 1, 1)) const;
+
+ virtual bool has_outline() const { return false; }
+ virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const = 0;
void update_changes();
Font();
};
+// Helper class to that draws outlines immediately and draws characters in its destructor.
+class FontDrawer {
+ const Ref<Font> &font;
+ Color outline_color;
+ bool has_outline;
+
+ struct PendingDraw {
+ RID canvas_item;
+ Point2 pos;
+ CharType chr;
+ CharType next;
+ Color modulate;
+ };
+
+ Vector<PendingDraw> pending_draws;
+
+public:
+ FontDrawer(const Ref<Font> &p_font, const Color &p_outline_color) :
+ font(p_font),
+ outline_color(p_outline_color) {
+ has_outline = p_font->has_outline();
+ }
+
+ float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) {
+ if (has_outline) {
+ PendingDraw draw = { p_canvas_item, p_pos, p_char, p_next, p_modulate };
+ pending_draws.push_back(draw);
+ }
+ return font->draw_char(p_canvas_item, p_pos, p_char, p_next, has_outline ? outline_color : p_modulate, has_outline);
+ }
+
+ ~FontDrawer() {
+ for (int i = 0; i < pending_draws.size(); ++i) {
+ const PendingDraw &draw = pending_draws[i];
+ font->draw_char(draw.canvas_item, draw.pos, draw.chr, draw.next, draw.modulate, false);
+ }
+ }
+};
+
class BitmapFont : public Font {
GDCLASS(BitmapFont, Font);
@@ -153,7 +194,7 @@ public:
void set_distance_field_hint(bool p_distance_field);
bool is_distance_field_hint() const;
- float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) const;
+ float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const;
BitmapFont();
~BitmapFont();
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index a83ef198fb..5e7569586a 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -387,10 +387,12 @@ void SpatialMaterial::_update_shader() {
if (flags[FLAG_USE_VERTEX_LIGHTING]) {
code += ",vertex_lighting";
}
-
if (flags[FLAG_TRIPLANAR_USE_WORLD] && (flags[FLAG_UV1_USE_TRIPLANAR] || flags[FLAG_UV2_USE_TRIPLANAR])) {
code += ",world_vertex_coords";
}
+ if (flags[FLAG_DONT_RECEIVE_SHADOWS]) {
+ code += ",shadows_disabled";
+ }
code += ";\n";
code += "uniform vec4 albedo : hint_color;\n";
@@ -550,7 +552,7 @@ void SpatialMaterial::_update_shader() {
//handle animation
code += "\tint particle_total_frames = particles_anim_h_frames * particles_anim_v_frames;\n";
- code += "\tint particle_frame = int(INSTANCE_CUSTOM.y * float(particle_total_frames));\n";
+ code += "\tint particle_frame = int(INSTANCE_CUSTOM.z * float(particle_total_frames));\n";
code += "\tif (particles_anim_loop) particle_frame=clamp(particle_frame,0,particle_total_frames-1); else particle_frame=abs(particle_frame)%particle_total_frames;\n";
code += "\tUV /= vec2(float(particles_anim_h_frames),float(particles_anim_v_frames));\n";
code += "\tUV += vec2(float(particle_frame % particles_anim_h_frames) / float(particles_anim_h_frames),float(particle_frame / particles_anim_h_frames) / float(particles_anim_v_frames));\n";
@@ -1849,6 +1851,7 @@ void SpatialMaterial::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_world_triplanar"), "set_flag", "get_flag", FLAG_TRIPLANAR_USE_WORLD);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_fixed_size"), "set_flag", "get_flag", FLAG_FIXED_SIZE);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_albedo_tex_force_srgb"), "set_flag", "get_flag", FLAG_ALBEDO_TEXTURE_FORCE_SRGB);
+ ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_do_not_receive_shadows"), "set_flag", "get_flag", FLAG_DONT_RECEIVE_SHADOWS);
ADD_GROUP("Vertex Color", "vertex_color");
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "vertex_color_use_as_albedo"), "set_flag", "get_flag", FLAG_ALBEDO_FROM_VERTEX_COLOR);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "vertex_color_is_srgb"), "set_flag", "get_flag", FLAG_SRGB_VERTEX_COLOR);
@@ -2038,6 +2041,7 @@ void SpatialMaterial::_bind_methods() {
BIND_ENUM_CONSTANT(FLAG_USE_ALPHA_SCISSOR);
BIND_ENUM_CONSTANT(FLAG_TRIPLANAR_USE_WORLD);
BIND_ENUM_CONSTANT(FLAG_ALBEDO_TEXTURE_FORCE_SRGB);
+ BIND_ENUM_CONSTANT(FLAG_DONT_RECEIVE_SHADOWS);
BIND_ENUM_CONSTANT(FLAG_MAX);
BIND_ENUM_CONSTANT(DIFFUSE_BURLEY);
diff --git a/scene/resources/material.h b/scene/resources/material.h
index 2c297cda41..ce733bfb8d 100644
--- a/scene/resources/material.h
+++ b/scene/resources/material.h
@@ -188,6 +188,7 @@ public:
FLAG_EMISSION_ON_UV2,
FLAG_USE_ALPHA_SCISSOR,
FLAG_ALBEDO_TEXTURE_FORCE_SRGB,
+ FLAG_DONT_RECEIVE_SHADOWS,
FLAG_MAX
};
@@ -236,7 +237,7 @@ private:
uint64_t blend_mode : 2;
uint64_t depth_draw_mode : 2;
uint64_t cull_mode : 2;
- uint64_t flags : 14;
+ uint64_t flags : 15;
uint64_t detail_blend_mode : 2;
uint64_t diffuse_mode : 3;
uint64_t specular_mode : 2;
diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp
index b832ea1239..b0620d3363 100644
--- a/scene/resources/mesh.cpp
+++ b/scene/resources/mesh.cpp
@@ -222,7 +222,6 @@ Ref<Mesh> Mesh::create_outline(float p_margin) const {
continue;
Array a = surface_get_arrays(i);
- int vcount = 0;
if (i == 0) {
arrays = a;
@@ -230,6 +229,7 @@ Ref<Mesh> Mesh::create_outline(float p_margin) const {
index_accum += v.size();
} else {
+ int vcount = 0;
for (int j = 0; j < arrays.size(); j++) {
if (arrays[j].get_type() == Variant::NIL || a[j].get_type() == Variant::NIL) {
@@ -912,6 +912,7 @@ void ArrayMesh::surface_set_material(int p_idx, const Ref<Material> &p_material)
VisualServer::get_singleton()->mesh_surface_set_material(mesh, p_idx, p_material.is_null() ? RID() : p_material->get_rid());
_change_notify("material");
+ emit_changed();
}
void ArrayMesh::surface_set_name(int p_idx, const String &p_name) {
@@ -919,6 +920,7 @@ void ArrayMesh::surface_set_name(int p_idx, const String &p_name) {
ERR_FAIL_INDEX(p_idx, surfaces.size());
surfaces[p_idx].name = p_name;
+ emit_changed();
}
String ArrayMesh::surface_get_name(int p_idx) const {
@@ -931,6 +933,7 @@ void ArrayMesh::surface_update_region(int p_surface, int p_offset, const PoolVec
ERR_FAIL_INDEX(p_surface, surfaces.size());
VS::get_singleton()->mesh_surface_update_region(mesh, p_surface, p_offset, p_data);
+ emit_changed();
}
void ArrayMesh::surface_set_custom_aabb(int p_idx, const AABB &p_aabb) {
@@ -938,6 +941,7 @@ void ArrayMesh::surface_set_custom_aabb(int p_idx, const AABB &p_aabb) {
ERR_FAIL_INDEX(p_idx, surfaces.size());
surfaces[p_idx].aabb = p_aabb;
// set custom aabb too?
+ emit_changed();
}
Ref<Material> ArrayMesh::surface_get_material(int p_idx) const {
@@ -986,6 +990,7 @@ void ArrayMesh::set_custom_aabb(const AABB &p_custom) {
custom_aabb = p_custom;
VS::get_singleton()->mesh_set_custom_aabb(mesh, custom_aabb);
+ emit_changed();
}
AABB ArrayMesh::get_custom_aabb() const {
@@ -1189,8 +1194,6 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe
for (int j = 0; j < 3; j++) {
- int vertex_idx = gen_vertices[gen_indices[i + j]];
-
SurfaceTool::Vertex v = surfaces[surface].vertices[uv_index[gen_vertices[gen_indices[i + j]]].second];
if (surfaces[surface].format & ARRAY_FORMAT_COLOR) {
diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp
index 94c54c91d3..5b600623b9 100644
--- a/scene/resources/primitive_meshes.cpp
+++ b/scene/resources/primitive_meshes.cpp
@@ -57,6 +57,31 @@ void PrimitiveMesh::_update() const {
}
}
+ if (flip_faces) {
+ PoolVector<Vector3> normals = arr[VS::ARRAY_NORMAL];
+ PoolVector<int> indices = arr[VS::ARRAY_INDEX];
+ if (normals.size() && indices.size()) {
+
+ {
+ int nc = normals.size();
+ PoolVector<Vector3>::Write w = normals.write();
+ for (int i = 0; i < nc; i++) {
+ w[i] = -w[i];
+ }
+ }
+
+ {
+ int ic = indices.size();
+ PoolVector<int>::Write w = indices.write();
+ for (int i = 0; i < ic; i += 3) {
+ SWAP(w[i + 0], w[i + 1]);
+ }
+ }
+ arr[VS::ARRAY_NORMAL] = normals;
+ arr[VS::ARRAY_INDEX] = indices;
+ }
+ }
+
// in with the new
VisualServer::get_singleton()->mesh_clear(mesh);
VisualServer::get_singleton()->mesh_add_surface_from_arrays(mesh, (VisualServer::PrimitiveType)primitive_type, arr);
@@ -65,6 +90,8 @@ void PrimitiveMesh::_update() const {
pending_request = false;
_clear_triangle_mesh();
+
+ const_cast<PrimitiveMesh *>(this)->emit_changed();
}
void PrimitiveMesh::_request_update() {
@@ -164,7 +191,15 @@ void PrimitiveMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_mesh_arrays"), &PrimitiveMesh::get_mesh_arrays);
+ ClassDB::bind_method(D_METHOD("set_custom_aabb", "aabb"), &PrimitiveMesh::set_custom_aabb);
+ ClassDB::bind_method(D_METHOD("get_custom_aabb"), &PrimitiveMesh::get_custom_aabb);
+
+ ClassDB::bind_method(D_METHOD("set_flip_faces", "flip_faces"), &PrimitiveMesh::set_flip_faces);
+ ClassDB::bind_method(D_METHOD("get_flip_faces"), &PrimitiveMesh::get_flip_faces);
+
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "SpatialMaterial,ShaderMaterial"), "set_material", "get_material");
+ ADD_PROPERTY(PropertyInfo(Variant::AABB, "custom_aabb", PROPERTY_HINT_NONE, ""), "set_custom_aabb", "get_custom_aabb");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_faces"), "set_flip_faces", "get_flip_faces");
}
void PrimitiveMesh::set_material(const Ref<Material> &p_material) {
@@ -185,7 +220,30 @@ Array PrimitiveMesh::get_mesh_arrays() const {
return surface_get_arrays(0);
}
+void PrimitiveMesh::set_custom_aabb(const AABB &p_custom) {
+
+ custom_aabb = p_custom;
+ VS::get_singleton()->mesh_set_custom_aabb(mesh, custom_aabb);
+ emit_changed();
+}
+
+AABB PrimitiveMesh::get_custom_aabb() const {
+
+ return custom_aabb;
+}
+
+void PrimitiveMesh::set_flip_faces(bool p_enable) {
+ flip_faces = p_enable;
+ _request_update();
+}
+
+bool PrimitiveMesh::get_flip_faces() const {
+ return flip_faces;
+}
+
PrimitiveMesh::PrimitiveMesh() {
+
+ flip_faces = false;
// defaults
mesh = VisualServer::get_singleton()->mesh_create();
@@ -618,7 +676,7 @@ void CubeMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_subdivide_depth", "divisions"), &CubeMesh::set_subdivide_depth);
ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &CubeMesh::get_subdivide_depth);
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size"), "set_size", "get_size");
ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_width", "get_subdivide_width");
ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_height", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_height", "get_subdivide_height");
ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_depth", "get_subdivide_depth");
diff --git a/scene/resources/primitive_meshes.h b/scene/resources/primitive_meshes.h
index 94a7a055a3..a91aa09ffe 100644
--- a/scene/resources/primitive_meshes.h
+++ b/scene/resources/primitive_meshes.h
@@ -48,8 +48,10 @@ class PrimitiveMesh : public Mesh {
private:
RID mesh;
mutable AABB aabb;
+ AABB custom_aabb;
Ref<Material> material;
+ bool flip_faces;
mutable bool pending_request;
void _update() const;
@@ -81,6 +83,12 @@ public:
Array get_mesh_arrays() const;
+ void set_custom_aabb(const AABB &p_custom);
+ AABB get_custom_aabb() const;
+
+ void set_flip_faces(bool p_enable);
+ bool get_flip_faces() const;
+
PrimitiveMesh();
~PrimitiveMesh();
};
diff --git a/scene/resources/style_box.cpp b/scene/resources/style_box.cpp
index 626fda50df..ebad00b068 100644
--- a/scene/resources/style_box.cpp
+++ b/scene/resources/style_box.cpp
@@ -106,7 +106,11 @@ void StyleBoxTexture::set_texture(Ref<Texture> p_texture) {
if (texture == p_texture)
return;
texture = p_texture;
- region_rect = Rect2(Point2(), texture->get_size());
+ if (p_texture.is_null()) {
+ region_rect = Rect2(0, 0, 0, 0);
+ } else {
+ region_rect = Rect2(Point2(), texture->get_size());
+ }
emit_signal("texture_changed");
emit_changed();
_change_notify("texture");
@@ -132,8 +136,17 @@ Ref<Texture> StyleBoxTexture::get_normal_map() const {
void StyleBoxTexture::set_margin_size(Margin p_margin, float p_size) {
+ ERR_FAIL_INDEX(p_margin, 4);
+
margin[p_margin] = p_size;
emit_changed();
+ static const char *margin_prop[4] = {
+ "content_margin_left",
+ "content_margin_top",
+ "content_margin_right",
+ "content_margin_bottom",
+ };
+ _change_notify(margin_prop[p_margin]);
}
float StyleBoxTexture::get_margin_size(Margin p_margin) const {
@@ -182,7 +195,7 @@ Size2 StyleBoxTexture::get_center_size() const {
if (texture.is_null())
return Size2();
- return texture->get_size() - get_minimum_size();
+ return region_rect.size - get_minimum_size();
}
void StyleBoxTexture::set_expand_margin_size(Margin p_expand_margin, float p_size) {
diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp
index c0f6756fd1..54f5aea160 100644
--- a/scene/resources/texture.cpp
+++ b/scene/resources/texture.cpp
@@ -76,7 +76,9 @@ void Texture::_bind_methods() {
ClassDB::bind_method(D_METHOD("draw_rect_region", "canvas_item", "rect", "src_rect", "modulate", "transpose", "normal_map", "clip_uv"), &Texture::draw_rect_region, DEFVAL(Color(1, 1, 1)), DEFVAL(false), DEFVAL(Variant()), DEFVAL(true));
ClassDB::bind_method(D_METHOD("get_data"), &Texture::get_data);
+ ADD_GROUP("Flags", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Mipmaps,Repeat,Filter,Anisotropic Linear,Convert to Linear,Mirrored Repeat,Video Surface"), "set_flags", "get_flags");
+ ADD_GROUP("", "");
BIND_ENUM_CONSTANT(FLAGS_DEFAULT);
BIND_ENUM_CONSTANT(FLAG_MIPMAPS);
@@ -220,12 +222,15 @@ Image::Format ImageTexture::get_format() const {
return format;
}
-void ImageTexture::load(const String &p_path) {
+Error ImageTexture::load(const String &p_path) {
Ref<Image> img;
img.instance();
- img->load(p_path);
- create_from_image(img);
+ Error err = img->load(p_path);
+ if (err == OK) {
+ create_from_image(img);
+ }
+ return err;
}
void ImageTexture::set_data(const Ref<Image> &p_image) {
diff --git a/scene/resources/texture.h b/scene/resources/texture.h
index 93d7ec4ef9..d81fd3b19b 100644
--- a/scene/resources/texture.h
+++ b/scene/resources/texture.h
@@ -124,7 +124,7 @@ public:
void set_flags(uint32_t p_flags);
uint32_t get_flags() const;
Image::Format get_format() const;
- void load(const String &p_path);
+ Error load(const String &p_path);
void set_data(const Ref<Image> &p_image);
Ref<Image> get_data() const;
diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp
index bebbf6e238..58057cda0c 100644
--- a/scene/resources/tile_set.cpp
+++ b/scene/resources/tile_set.cpp
@@ -142,6 +142,8 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) {
tile_set_navigation_polygon(id, p_value);
else if (what == "navigation_offset")
tile_set_navigation_polygon_offset(id, p_value);
+ else if (what == "z_index")
+ tile_set_z_index(id, p_value);
else
return false;
@@ -239,6 +241,8 @@ bool TileSet::_get(const StringName &p_name, Variant &r_ret) const {
r_ret = tile_get_navigation_polygon(id);
else if (what == "navigation_offset")
r_ret = tile_get_navigation_polygon_offset(id);
+ else if (what == "z_index")
+ r_ret = tile_get_z_index(id);
else
return false;
@@ -260,7 +264,7 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::RECT2, pre + "region"));
p_list->push_back(PropertyInfo(Variant::INT, pre + "tile_mode", PROPERTY_HINT_ENUM, "SINGLE_TILE,AUTO_TILE"));
if (tile_get_tile_mode(id) == AUTO_TILE) {
- p_list->push_back(PropertyInfo(Variant::INT, pre + "autotile/bitmask_mode", PROPERTY_HINT_ENUM, "2X2,3X3", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::INT, pre + "autotile/bitmask_mode", PROPERTY_HINT_ENUM, "2X2,3X3 (minimal),3X3", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/bitmask_flags", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "autotile/icon_coordinate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "autotile/tile_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
@@ -278,6 +282,7 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::OBJECT, pre + "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape2D", PROPERTY_USAGE_EDITOR));
p_list->push_back(PropertyInfo(Variant::BOOL, pre + "shape_one_way", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR));
p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "shapes", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::INT, pre + "z_index", PROPERTY_HINT_RANGE, itos(VS::CANVAS_ITEM_Z_MIN) + "," + itos(VS::CANVAS_ITEM_Z_MAX) + ",1"));
}
}
@@ -748,6 +753,19 @@ Vector<TileSet::ShapeData> TileSet::tile_get_shapes(int p_id) const {
return tile_map[p_id].shapes_data;
}
+int TileSet::tile_get_z_index(int p_id) const {
+
+ ERR_FAIL_COND_V(!tile_map.has(p_id), 0);
+ return tile_map[p_id].z_index;
+}
+
+void TileSet::tile_set_z_index(int p_id, int p_z_index) {
+
+ ERR_FAIL_COND(!tile_map.has(p_id));
+ tile_map[p_id].z_index = p_z_index;
+ emit_changed();
+}
+
void TileSet::_tile_set_shapes(int p_id, const Array &p_shapes) {
ERR_FAIL_COND(!tile_map.has(p_id));
@@ -929,6 +947,8 @@ void TileSet::_bind_methods() {
ClassDB::bind_method(D_METHOD("tile_get_light_occluder", "id"), &TileSet::tile_get_light_occluder);
ClassDB::bind_method(D_METHOD("tile_set_occluder_offset", "id", "occluder_offset"), &TileSet::tile_set_occluder_offset);
ClassDB::bind_method(D_METHOD("tile_get_occluder_offset", "id"), &TileSet::tile_get_occluder_offset);
+ ClassDB::bind_method(D_METHOD("tile_set_z_index", "id", "z_index"), &TileSet::tile_set_z_index);
+ ClassDB::bind_method(D_METHOD("tile_get_z_index", "id"), &TileSet::tile_get_z_index);
ClassDB::bind_method(D_METHOD("remove_tile", "id"), &TileSet::remove_tile);
ClassDB::bind_method(D_METHOD("clear"), &TileSet::clear);
@@ -940,6 +960,7 @@ void TileSet::_bind_methods() {
BIND_VMETHOD(MethodInfo(Variant::VECTOR2, "_forward_subtile_selection", PropertyInfo(Variant::INT, "autotile_id"), PropertyInfo(Variant::INT, "bitmask"), PropertyInfo(Variant::OBJECT, "tilemap", PROPERTY_HINT_NONE, "TileMap"), PropertyInfo(Variant::VECTOR2, "tile_location")));
BIND_ENUM_CONSTANT(BITMASK_2X2);
+ BIND_ENUM_CONSTANT(BITMASK_3X3_MINIMAL);
BIND_ENUM_CONSTANT(BITMASK_3X3);
BIND_ENUM_CONSTANT(BIND_TOPLEFT);
diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h
index 706d04998f..ec635ee5cc 100644
--- a/scene/resources/tile_set.h
+++ b/scene/resources/tile_set.h
@@ -56,6 +56,7 @@ public:
enum BitmaskMode {
BITMASK_2X2,
+ BITMASK_3X3_MINIMAL,
BITMASK_3X3
};
@@ -113,11 +114,13 @@ private:
Color modulate;
TileMode tile_mode;
AutotileData autotile_data;
+ int z_index;
// Default modulate for back-compat
explicit TileData() :
tile_mode(SINGLE_TILE),
- modulate(1, 1, 1) {}
+ modulate(1, 1, 1),
+ z_index(0) {}
};
Map<int, TileData> tile_map;
@@ -220,6 +223,9 @@ public:
Ref<NavigationPolygon> autotile_get_navigation_polygon(int p_id, const Vector2 &p_coord) const;
const Map<Vector2, Ref<NavigationPolygon> > &autotile_get_navigation_map(int p_id) const;
+ void tile_set_z_index(int p_id, int p_z_index);
+ int tile_get_z_index(int p_id) const;
+
void remove_tile(int p_id);
bool has_tile(int p_id) const;