diff options
Diffstat (limited to 'scene/resources')
97 files changed, 15258 insertions, 41826 deletions
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 6f64ac6d04..640ec50eb9 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -43,8 +43,8 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { if (tracks.size() == track && what == "type") { String type = p_value; - if (type == "transform") { - add_track(TYPE_TRANSFORM); + if (type == "transform" || type == "transform3d") { + add_track(TYPE_TRANSFORM3D); } else if (type == "value") { add_track(TYPE_VALUE); } else if (type == "method") { @@ -75,7 +75,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { } else if (what == "enabled") { track_set_enabled(track, p_value); } else if (what == "keys" || what == "key_values") { - if (track_get_type(track) == TYPE_TRANSFORM) { + if (track_get_type(track) == TYPE_TRANSFORM3D) { TransformTrack *tt = static_cast<TransformTrack *>(tracks[track]); Vector<float> values = p_value; int vcount = values.size(); @@ -318,7 +318,7 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { ERR_FAIL_INDEX_V(track, tracks.size(), false); if (what == "type") { switch (track_get_type(track)) { - case TYPE_TRANSFORM: + case TYPE_TRANSFORM3D: r_ret = "transform"; break; case TYPE_VALUE: @@ -351,7 +351,7 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { } else if (what == "enabled") { r_ret = track_is_enabled(track); } else if (what == "keys") { - if (track_get_type(track) == TYPE_TRANSFORM) { + if (track_get_type(track) == TYPE_TRANSFORM3D) { Vector<float> keys; int kk = track_get_key_count(track); keys.resize(kk * 12); @@ -361,7 +361,7 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { int idx = 0; for (int i = 0; i < track_get_key_count(track); i++) { Vector3 loc; - Quat rot; + Quaternion rot; Vector3 scale; transform_track_get_key(track, i, &loc, &rot, &scale); @@ -590,7 +590,7 @@ int Animation::add_track(TrackType p_type, int p_at_pos) { } switch (p_type) { - case TYPE_TRANSFORM: { + case TYPE_TRANSFORM3D: { TransformTrack *tt = memnew(TransformTrack); tracks.insert(p_at_pos, tt); } break; @@ -628,7 +628,7 @@ void Animation::remove_track(int p_track) { Track *t = tracks[p_track]; switch (t->type) { - case TYPE_TRANSFORM: { + case TYPE_TRANSFORM3D: { TransformTrack *tt = static_cast<TransformTrack *>(t); _clear(tt->transforms); @@ -671,7 +671,7 @@ int Animation::get_track_count() const { } Animation::TrackType Animation::track_get_type(int p_track) const { - ERR_FAIL_INDEX_V(p_track, tracks.size(), TYPE_TRANSFORM); + ERR_FAIL_INDEX_V(p_track, tracks.size(), TYPE_TRANSFORM3D); return tracks[p_track]->type; } @@ -773,12 +773,12 @@ void Animation::_clear(T &p_keys) { p_keys.clear(); } -Error Animation::transform_track_get_key(int p_track, int p_key, Vector3 *r_loc, Quat *r_rot, Vector3 *r_scale) const { +Error Animation::transform_track_get_key(int p_track, int p_key, Vector3 *r_loc, Quaternion *r_rot, Vector3 *r_scale) const { ERR_FAIL_INDEX_V(p_track, tracks.size(), ERR_INVALID_PARAMETER); Track *t = tracks[p_track]; TransformTrack *tt = static_cast<TransformTrack *>(t); - ERR_FAIL_COND_V(t->type != TYPE_TRANSFORM, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(t->type != TYPE_TRANSFORM3D, ERR_INVALID_PARAMETER); ERR_FAIL_INDEX_V(p_key, tt->transforms.size(), ERR_INVALID_PARAMETER); if (r_loc) { @@ -794,10 +794,10 @@ Error Animation::transform_track_get_key(int p_track, int p_key, Vector3 *r_loc, return OK; } -int Animation::transform_track_insert_key(int p_track, float p_time, const Vector3 &p_loc, const Quat &p_rot, const Vector3 &p_scale) { +int Animation::transform_track_insert_key(int p_track, float p_time, const Vector3 &p_loc, const Quaternion &p_rot, const Vector3 &p_scale) { ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); Track *t = tracks[p_track]; - ERR_FAIL_COND_V(t->type != TYPE_TRANSFORM, -1); + ERR_FAIL_COND_V(t->type != TYPE_TRANSFORM3D, -1); TransformTrack *tt = static_cast<TransformTrack *>(t); @@ -823,7 +823,7 @@ void Animation::track_remove_key(int p_track, int p_idx) { Track *t = tracks[p_track]; switch (t->type) { - case TYPE_TRANSFORM: { + case TYPE_TRANSFORM3D: { TransformTrack *tt = static_cast<TransformTrack *>(t); ERR_FAIL_INDEX(p_idx, tt->transforms.size()); tt->transforms.remove(p_idx); @@ -869,7 +869,7 @@ int Animation::track_find_key(int p_track, float p_time, bool p_exact) const { Track *t = tracks[p_track]; switch (t->type) { - case TYPE_TRANSFORM: { + case TYPE_TRANSFORM3D: { TransformTrack *tt = static_cast<TransformTrack *>(t); int k = _find(tt->transforms, p_time); if (k < 0 || k >= tt->transforms.size()) { @@ -951,14 +951,14 @@ void Animation::track_insert_key(int p_track, float p_time, const Variant &p_key Track *t = tracks[p_track]; switch (t->type) { - case TYPE_TRANSFORM: { + case TYPE_TRANSFORM3D: { Dictionary d = p_key; Vector3 loc; if (d.has("location")) { loc = d["location"]; } - Quat rot; + Quaternion rot; if (d.has("rotation")) { rot = d["rotation"]; } @@ -1053,7 +1053,7 @@ int Animation::track_get_key_count(int p_track) const { Track *t = tracks[p_track]; switch (t->type) { - case TYPE_TRANSFORM: { + case TYPE_TRANSFORM3D: { TransformTrack *tt = static_cast<TransformTrack *>(t); return tt->transforms.size(); } break; @@ -1088,7 +1088,7 @@ Variant Animation::track_get_key_value(int p_track, int p_key_idx) const { Track *t = tracks[p_track]; switch (t->type) { - case TYPE_TRANSFORM: { + case TYPE_TRANSFORM3D: { TransformTrack *tt = static_cast<TransformTrack *>(t); ERR_FAIL_INDEX_V(p_key_idx, tt->transforms.size(), Variant()); @@ -1156,7 +1156,7 @@ float Animation::track_get_key_time(int p_track, int p_key_idx) const { Track *t = tracks[p_track]; switch (t->type) { - case TYPE_TRANSFORM: { + case TYPE_TRANSFORM3D: { TransformTrack *tt = static_cast<TransformTrack *>(t); ERR_FAIL_INDEX_V(p_key_idx, tt->transforms.size(), -1); return tt->transforms[p_key_idx].time; @@ -1201,7 +1201,7 @@ void Animation::track_set_key_time(int p_track, int p_key_idx, float p_time) { Track *t = tracks[p_track]; switch (t->type) { - case TYPE_TRANSFORM: { + case TYPE_TRANSFORM3D: { TransformTrack *tt = static_cast<TransformTrack *>(t); ERR_FAIL_INDEX(p_key_idx, tt->transforms.size()); TKey<TransformKey> key = tt->transforms[p_key_idx]; @@ -1265,7 +1265,7 @@ float Animation::track_get_key_transition(int p_track, int p_key_idx) const { Track *t = tracks[p_track]; switch (t->type) { - case TYPE_TRANSFORM: { + case TYPE_TRANSFORM3D: { TransformTrack *tt = static_cast<TransformTrack *>(t); ERR_FAIL_INDEX_V(p_key_idx, tt->transforms.size(), -1); return tt->transforms[p_key_idx].transition; @@ -1301,7 +1301,7 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p Track *t = tracks[p_track]; switch (t->type) { - case TYPE_TRANSFORM: { + case TYPE_TRANSFORM3D: { TransformTrack *tt = static_cast<TransformTrack *>(t); ERR_FAIL_INDEX(p_key_idx, tt->transforms.size()); @@ -1384,7 +1384,7 @@ void Animation::track_set_key_transition(int p_track, int p_key_idx, float p_tra Track *t = tracks[p_track]; switch (t->type) { - case TYPE_TRANSFORM: { + case TYPE_TRANSFORM3D: { TransformTrack *tt = static_cast<TransformTrack *>(t); ERR_FAIL_INDEX(p_key_idx, tt->transforms.size()); tt->transforms.write[p_key_idx].transition = p_transition; @@ -1462,7 +1462,7 @@ Vector3 Animation::_interpolate(const Vector3 &p_a, const Vector3 &p_b, float p_ return p_a.lerp(p_b, p_c); } -Quat Animation::_interpolate(const Quat &p_a, const Quat &p_b, float p_c) const { +Quaternion Animation::_interpolate(const Quaternion &p_a, const Quaternion &p_b, float p_c) const { return p_a.slerp(p_b, p_c); } @@ -1490,7 +1490,7 @@ Vector3 Animation::_cubic_interpolate(const Vector3 &p_pre_a, const Vector3 &p_a return p_a.cubic_interpolate(p_b, p_pre_a, p_post_b, p_c); } -Quat Animation::_cubic_interpolate(const Quat &p_pre_a, const Quat &p_a, const Quat &p_b, const Quat &p_post_b, float p_c) const { +Quaternion Animation::_cubic_interpolate(const Quaternion &p_pre_a, const Quaternion &p_a, const Quaternion &p_b, const Quaternion &p_post_b, float p_c) const { return p_a.cubic_slerp(p_b, p_pre_a, p_post_b, p_c); } @@ -1555,11 +1555,11 @@ Variant Animation::_cubic_interpolate(const Variant &p_pre_a, const Variant &p_a return a.cubic_interpolate(b, pa, pb, p_c); } - case Variant::QUAT: { - Quat a = p_a; - Quat b = p_b; - Quat pa = p_pre_a; - Quat pb = p_post_b; + case Variant::QUATERNION: { + Quaternion a = p_a; + Quaternion b = p_b; + Quaternion pa = p_pre_a; + Quaternion pb = p_post_b; return a.cubic_slerp(b, pa, pb, p_c); } @@ -1728,10 +1728,10 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, float p_time, Interpola // do a barrel roll } -Error Animation::transform_track_interpolate(int p_track, float p_time, Vector3 *r_loc, Quat *r_rot, Vector3 *r_scale) const { +Error Animation::transform_track_interpolate(int p_track, float p_time, Vector3 *r_loc, Quaternion *r_rot, Vector3 *r_scale) const { ERR_FAIL_INDEX_V(p_track, tracks.size(), ERR_INVALID_PARAMETER); Track *t = tracks[p_track]; - ERR_FAIL_COND_V(t->type != TYPE_TRANSFORM, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(t->type != TYPE_TRANSFORM3D, ERR_INVALID_PARAMETER); TransformTrack *tt = static_cast<TransformTrack *>(t); @@ -1932,7 +1932,7 @@ void Animation::track_get_key_indices_in_range(int p_track, float p_time, float // handle loop by splitting switch (t->type) { - case TYPE_TRANSFORM: { + case TYPE_TRANSFORM3D: { 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); @@ -1988,7 +1988,7 @@ void Animation::track_get_key_indices_in_range(int p_track, float p_time, float } switch (t->type) { - case TYPE_TRANSFORM: { + case TYPE_TRANSFORM3D: { const TransformTrack *tt = static_cast<const TransformTrack *>(t); _track_get_key_indices_in_range(tt->transforms, from_time, to_time, p_indices); @@ -2681,7 +2681,7 @@ void Animation::_bind_methods() { ADD_SIGNAL(MethodInfo("tracks_changed")); BIND_ENUM_CONSTANT(TYPE_VALUE); - BIND_ENUM_CONSTANT(TYPE_TRANSFORM); + BIND_ENUM_CONSTANT(TYPE_TRANSFORM3D); BIND_ENUM_CONSTANT(TYPE_METHOD); BIND_ENUM_CONSTANT(TYPE_BEZIER); BIND_ENUM_CONSTANT(TYPE_AUDIO); @@ -2751,9 +2751,9 @@ bool Animation::_transform_track_optimize_key(const TKey<TransformKey> &t0, cons { //rotation - const Quat &q0 = t0.value.rot; - const Quat &q1 = t1.value.rot; - const Quat &q2 = t2.value.rot; + const Quaternion &q0 = t0.value.rot; + const Quaternion &q1 = t1.value.rot; + const Quaternion &q2 = t2.value.rot; //localize both to rotation from q0 @@ -2763,8 +2763,8 @@ bool Animation::_transform_track_optimize_key(const TKey<TransformKey> &t0, cons } } else { - Quat r02 = (q0.inverse() * q2).normalized(); - Quat r01 = (q0.inverse() * q1).normalized(); + Quaternion r02 = (q0.inverse() * q2).normalized(); + Quaternion r01 = (q0.inverse() * q1).normalized(); Vector3 v02, v01; real_t a02, a01; @@ -2877,7 +2877,7 @@ bool Animation::_transform_track_optimize_key(const TKey<TransformKey> &t0, cons void Animation::_transform_track_optimize(int p_idx, float p_allowed_linear_err, float p_allowed_angular_err, float p_max_optimizable_angle) { ERR_FAIL_INDEX(p_idx, tracks.size()); - ERR_FAIL_COND(tracks[p_idx]->type != TYPE_TRANSFORM); + ERR_FAIL_COND(tracks[p_idx]->type != TYPE_TRANSFORM3D); TransformTrack *tt = static_cast<TransformTrack *>(tracks[p_idx]); bool prev_erased = false; TKey<TransformKey> first_erased; @@ -2917,7 +2917,7 @@ void Animation::_transform_track_optimize(int p_idx, float p_allowed_linear_err, void Animation::optimize(float p_allowed_linear_err, float p_allowed_angular_err, float p_max_optimizable_angle) { for (int i = 0; i < tracks.size(); i++) { - if (tracks[i]->type == TYPE_TRANSFORM) { + if (tracks[i]->type == TYPE_TRANSFORM3D) { _transform_track_optimize(i, p_allowed_linear_err, p_allowed_angular_err, p_max_optimizable_angle); } } diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 66bc71c834..1484007333 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -42,7 +42,7 @@ class Animation : public Resource { public: enum TrackType { TYPE_VALUE, ///< Set a value in a property, can be interpolated. - TYPE_TRANSFORM, ///< Transform a node or a bone. + TYPE_TRANSFORM3D, ///< Transform a node or a bone. TYPE_METHOD, ///< Call any method on a specific node. TYPE_BEZIER, ///< Bezier curve TYPE_AUDIO, @@ -88,7 +88,7 @@ private: struct TransformKey { Vector3 loc; - Quat rot; + Quaternion rot; Vector3 scale; }; @@ -97,7 +97,7 @@ private: struct TransformTrack : public Track { Vector<TKey<TransformKey>> transforms; - TransformTrack() { type = TYPE_TRANSFORM; } + TransformTrack() { type = TYPE_TRANSFORM3D; } }; /* PROPERTY VALUE TRACK */ @@ -186,13 +186,13 @@ private: _FORCE_INLINE_ Animation::TransformKey _interpolate(const Animation::TransformKey &p_a, const Animation::TransformKey &p_b, float p_c) const; _FORCE_INLINE_ Vector3 _interpolate(const Vector3 &p_a, const Vector3 &p_b, float p_c) const; - _FORCE_INLINE_ Quat _interpolate(const Quat &p_a, const Quat &p_b, float p_c) const; + _FORCE_INLINE_ Quaternion _interpolate(const Quaternion &p_a, const Quaternion &p_b, float p_c) const; _FORCE_INLINE_ Variant _interpolate(const Variant &p_a, const Variant &p_b, float p_c) const; _FORCE_INLINE_ float _interpolate(const float &p_a, const float &p_b, float p_c) const; _FORCE_INLINE_ Animation::TransformKey _cubic_interpolate(const Animation::TransformKey &p_pre_a, const Animation::TransformKey &p_a, const Animation::TransformKey &p_b, const Animation::TransformKey &p_post_b, float p_c) const; _FORCE_INLINE_ Vector3 _cubic_interpolate(const Vector3 &p_pre_a, const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_post_b, float p_c) const; - _FORCE_INLINE_ Quat _cubic_interpolate(const Quat &p_pre_a, const Quat &p_a, const Quat &p_b, const Quat &p_post_b, float p_c) const; + _FORCE_INLINE_ Quaternion _cubic_interpolate(const Quaternion &p_pre_a, const Quaternion &p_a, const Quaternion &p_b, const Quaternion &p_post_b, float p_c) const; _FORCE_INLINE_ Variant _cubic_interpolate(const Variant &p_pre_a, const Variant &p_a, const Variant &p_b, const Variant &p_post_b, float p_c) const; _FORCE_INLINE_ float _cubic_interpolate(const float &p_pre_a, const float &p_a, const float &p_b, const float &p_post_b, float p_c) const; @@ -213,7 +213,7 @@ private: private: Array _transform_track_interpolate(int p_track, float p_time) const { Vector3 loc; - Quat rot; + Quaternion rot; Vector3 scale; transform_track_interpolate(p_track, p_time, &loc, &rot, &scale); Array ret; @@ -291,8 +291,8 @@ 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; + int transform_track_insert_key(int p_track, float p_time, const Vector3 &p_loc, const Quaternion &p_rot = Quaternion(), const Vector3 &p_scale = Vector3()); + Error transform_track_get_key(int p_track, int p_key, Vector3 *r_loc, Quaternion *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; @@ -321,7 +321,7 @@ public: void track_set_interpolation_loop_wrap(int p_track, bool p_enable); bool track_get_interpolation_loop_wrap(int p_track) const; - Error transform_track_interpolate(int p_track, float p_time, Vector3 *r_loc, Quat *r_rot, Vector3 *r_scale) const; + Error transform_track_interpolate(int p_track, float p_time, Vector3 *r_loc, Quaternion *r_rot, Vector3 *r_scale) const; Variant value_track_interpolate(int p_track, float p_time) const; void value_track_get_key_indices(int p_track, float p_time, float p_delta, List<int> *p_indices) const; diff --git a/scene/resources/audio_stream_sample.cpp b/scene/resources/audio_stream_sample.cpp index 06a91fb2f8..8ffd2df112 100644 --- a/scene/resources/audio_stream_sample.cpp +++ b/scene/resources/audio_stream_sample.cpp @@ -30,8 +30,8 @@ #include "audio_stream_sample.h" +#include "core/io/file_access.h" #include "core/io/marshalls.h" -#include "core/os/file_access.h" void AudioStreamPlaybackSample::start(float p_from_pos) { if (base->format == AudioStreamSample::FORMAT_IMA_ADPCM) { @@ -490,9 +490,9 @@ void AudioStreamSample::set_data(const Vector<uint8_t> &p_data) { const uint8_t *r = p_data.ptr(); int alloc_len = datalen + DATA_PAD * 2; data = memalloc(alloc_len); //alloc with some padding for interpolation - zeromem(data, alloc_len); + memset(data, 0, alloc_len); uint8_t *dataptr = (uint8_t *)data; - copymem(dataptr + DATA_PAD, r, datalen); + memcpy(dataptr + DATA_PAD, r, datalen); data_bytes = datalen; } @@ -507,7 +507,7 @@ Vector<uint8_t> AudioStreamSample::get_data() const { { uint8_t *w = pv.ptrw(); uint8_t *dataptr = (uint8_t *)data; - copymem(w, dataptr + DATA_PAD, data_bytes); + memcpy(w, dataptr + DATA_PAD, data_bytes); } } @@ -596,7 +596,7 @@ Error AudioStreamSample::save_to_wav(const String &p_path) { Ref<AudioStreamPlayback> AudioStreamSample::instance_playback() { Ref<AudioStreamPlaybackSample> sample; - sample.instance(); + sample.instantiate(); sample->base = Ref<AudioStreamSample>(this); return sample; } diff --git a/scene/resources/bit_map.cpp b/scene/resources/bit_map.cpp index 3cc1af59ae..de557494c3 100644 --- a/scene/resources/bit_map.cpp +++ b/scene/resources/bit_map.cpp @@ -38,8 +38,8 @@ void BitMap::create(const Size2 &p_size) { width = p_size.width; height = p_size.height; - bitmask.resize(((width * height) / 8) + 1); - zeromem(bitmask.ptrw(), bitmask.size()); + bitmask.resize((((width * height) - 1) / 8) + 1); + memset(bitmask.ptrw(), 0, bitmask.size()); } void BitMap::create_from_image_alpha(const Ref<Image> &p_image, float p_threshold) { @@ -487,7 +487,7 @@ Vector<Vector<Vector2>> BitMap::clip_opaque_to_polygons(const Rect2 &p_rect, flo Point2i from; Ref<BitMap> fill; - fill.instance(); + fill.instantiate(); fill->create(get_size()); Vector<Vector<Vector2>> polygons; @@ -525,7 +525,7 @@ void BitMap::grow_mask(int p_pixels, const Rect2 &p_rect) { Rect2i r = Rect2i(0, 0, width, height).intersection(p_rect); Ref<BitMap> copy; - copy.instance(); + copy.instantiate(); copy->create(get_size()); copy->bitmask = bitmask; @@ -604,7 +604,7 @@ Array BitMap::_opaque_to_polygons_bind(const Rect2 &p_rect, float p_epsilon) con void BitMap::resize(const Size2 &p_new_size) { Ref<BitMap> new_bitmap; - new_bitmap.instance(); + new_bitmap.instantiate(); new_bitmap->create(p_new_size); int lw = MIN(width, p_new_size.width); int lh = MIN(height, p_new_size.height); @@ -621,7 +621,7 @@ void BitMap::resize(const Size2 &p_new_size) { Ref<Image> BitMap::convert_to_image() const { Ref<Image> image; - image.instance(); + image.instantiate(); image->create(width, height, false, Image::FORMAT_L8); for (int i = 0; i < width; i++) { diff --git a/scene/resources/box_shape_3d.cpp b/scene/resources/box_shape_3d.cpp index 6e7adc0bd7..008914c5ee 100644 --- a/scene/resources/box_shape_3d.cpp +++ b/scene/resources/box_shape_3d.cpp @@ -56,6 +56,26 @@ void BoxShape3D::_update_shape() { Shape3D::_update_shape(); } +#ifndef DISABLE_DEPRECATED +bool BoxShape3D::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "extents") { // Compatibility with Godot 3.x. + // Convert to `size`, twice as big. + set_size((Vector3)p_value * 2); + return true; + } + return false; +} + +bool BoxShape3D::_get(const StringName &p_name, Variant &r_property) const { + if (p_name == "extents") { // Compatibility with Godot 3.x. + // Convert to `extents`, half as big. + r_property = size / 2; + return true; + } + return false; +} +#endif // DISABLE_DEPRECATED + void BoxShape3D::set_size(const Vector3 &p_size) { size = p_size; _update_shape(); diff --git a/scene/resources/box_shape_3d.h b/scene/resources/box_shape_3d.h index fce05d61ed..91978a0e6a 100644 --- a/scene/resources/box_shape_3d.h +++ b/scene/resources/box_shape_3d.h @@ -39,6 +39,10 @@ class BoxShape3D : public Shape3D { protected: static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_property) const; +#endif // DISABLE_DEPRECATED virtual void _update_shape() override; diff --git a/scene/resources/camera_effects.cpp b/scene/resources/camera_effects.cpp index 34c6bc05bc..b633196424 100644 --- a/scene/resources/camera_effects.cpp +++ b/scene/resources/camera_effects.cpp @@ -175,11 +175,11 @@ void CameraEffects::_bind_methods() { ADD_GROUP("DOF Blur", "dof_blur_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dof_blur_far_enabled"), "set_dof_blur_far_enabled", "is_dof_blur_far_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_far_distance", PROPERTY_HINT_EXP_RANGE, "0.01,8192,0.01"), "set_dof_blur_far_distance", "get_dof_blur_far_distance"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_far_transition", PROPERTY_HINT_EXP_RANGE, "0.01,8192,0.01"), "set_dof_blur_far_transition", "get_dof_blur_far_transition"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_far_distance", PROPERTY_HINT_RANGE, "0.01,8192,0.01,exp"), "set_dof_blur_far_distance", "get_dof_blur_far_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_far_transition", PROPERTY_HINT_RANGE, "0.01,8192,0.01,exp"), "set_dof_blur_far_transition", "get_dof_blur_far_transition"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dof_blur_near_enabled"), "set_dof_blur_near_enabled", "is_dof_blur_near_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_near_distance", PROPERTY_HINT_EXP_RANGE, "0.01,8192,0.01"), "set_dof_blur_near_distance", "get_dof_blur_near_distance"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_near_transition", PROPERTY_HINT_EXP_RANGE, "0.01,8192,0.01"), "set_dof_blur_near_transition", "get_dof_blur_near_transition"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_near_distance", PROPERTY_HINT_RANGE, "0.01,8192,0.01,exp"), "set_dof_blur_near_distance", "get_dof_blur_near_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_near_transition", PROPERTY_HINT_RANGE, "0.01,8192,0.01,exp"), "set_dof_blur_near_transition", "get_dof_blur_near_transition"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "dof_blur_amount", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_dof_blur_amount", "get_dof_blur_amount"); // Override exposure diff --git a/scene/resources/convex_polygon_shape_2d.cpp b/scene/resources/convex_polygon_shape_2d.cpp index 6e56f6e7fc..ac31315858 100644 --- a/scene/resources/convex_polygon_shape_2d.cpp +++ b/scene/resources/convex_polygon_shape_2d.cpp @@ -41,7 +41,7 @@ bool ConvexPolygonShape2D::_edit_is_selected_on_click(const Point2 &p_point, dou void ConvexPolygonShape2D::_update_shape() { Vector<Vector2> final_points = points; if (Geometry2D::is_polygon_clockwise(final_points)) { //needs to be counter clockwise - final_points.invert(); + final_points.reverse(); } PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), final_points); emit_changed(); diff --git a/scene/resources/convex_polygon_shape_3d.cpp b/scene/resources/convex_polygon_shape_3d.cpp index 9e030bc077..6b895da606 100644 --- a/scene/resources/convex_polygon_shape_3d.cpp +++ b/scene/resources/convex_polygon_shape_3d.cpp @@ -29,7 +29,7 @@ /*************************************************************************/ #include "convex_polygon_shape_3d.h" -#include "core/math/quick_hull.h" +#include "core/math/convex_hull.h" #include "servers/physics_server_3d.h" Vector<Vector3> ConvexPolygonShape3D::get_debug_mesh_lines() const { @@ -38,7 +38,7 @@ Vector<Vector3> ConvexPolygonShape3D::get_debug_mesh_lines() const { if (points.size() > 3) { Vector<Vector3> varr = Variant(points); Geometry3D::MeshData md; - Error err = QuickHull::build(varr, md); + Error err = ConvexHullComputer::convex_hull(varr, md); if (err == OK) { Vector<Vector3> lines; lines.resize(md.edges.size() * 2); diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index bc479e557a..846da39221 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -445,10 +445,10 @@ void Curve::set_bake_resolution(int p_resolution) { _baked_cache_dirty = true; } -real_t Curve::interpolate_baked(real_t offset) { +real_t Curve::interpolate_baked(real_t offset) const { if (_baked_cache_dirty) { // Last-second bake if not done already - bake(); + const_cast<Curve *>(this)->bake(); } // Special cases if the cache is too small diff --git a/scene/resources/curve.h b/scene/resources/curve.h index 402c893cd8..746c6fa597 100644 --- a/scene/resources/curve.h +++ b/scene/resources/curve.h @@ -122,7 +122,7 @@ public: void bake(); int get_bake_resolution() const { return _bake_resolution; } void set_bake_resolution(int p_resolution); - real_t interpolate_baked(real_t offset); + real_t interpolate_baked(real_t offset) const; void ensure_default_setup(float p_min, float p_max); diff --git a/scene/resources/default_theme/SCsub b/scene/resources/default_theme/SCsub index fc61250247..0fb6bb2c62 100644 --- a/scene/resources/default_theme/SCsub +++ b/scene/resources/default_theme/SCsub @@ -2,4 +2,16 @@ Import("env") +import os +import os.path +from platform_methods import run_in_subprocess +import default_theme_builders + env.add_source_files(env.scene_sources, "*.cpp") + +env.Depends("#scene/resources/default_theme/default_font.gen.h", "#thirdparty/fonts/OpenSans_SemiBold.ttf") +env.CommandNoCache( + "#scene/resources/default_theme/default_font.gen.h", + "#thirdparty/fonts/OpenSans_SemiBold.ttf", + run_in_subprocess(default_theme_builders.make_fonts_header), +) diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 854001f397..1bbb84f43d 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -30,15 +30,12 @@ #include "default_theme.h" -#include "scene/resources/theme.h" - #include "core/os/os.h" -#include "theme_data.h" - -#include "font_hidpi.inc" -#include "font_lodpi.inc" - +#include "default_font.gen.h" +#include "scene/resources/font.h" +#include "scene/resources/theme.h" #include "servers/text_server.h" +#include "theme_data.h" typedef Map<const void *, Ref<ImageTexture>> TexCacheMap; @@ -114,7 +111,7 @@ static Ref<Texture2D> flip_icon(Ref<Texture2D> p_texture, bool p_flip_y = false, } Ref<ImageTexture> texture(memnew(ImageTexture)); - Ref<Image> img = p_texture->get_data(); + Ref<Image> img = p_texture->get_image(); img = img->duplicate(); if (p_flip_y) { @@ -128,38 +125,6 @@ static Ref<Texture2D> flip_icon(Ref<Texture2D> p_texture, bool p_flip_y = false, return texture; } -static Ref<FontData> make_font(int p_height, int p_ascent, int p_charcount, const int *p_char_rects, int p_kerning_count, const int *p_kernings, int p_w, int p_h, const unsigned char *p_img) { - Ref<FontData> font(memnew(FontData)); - font->new_bitmap(p_height, p_ascent, p_height); - - Ref<Image> image = memnew(Image(p_img)); - Ref<ImageTexture> tex = memnew(ImageTexture); - tex->create_from_image(image); - - font->bitmap_add_texture(tex); - - for (int i = 0; i < p_charcount; i++) { - const int *c = &p_char_rects[i * 8]; - - int chr = c[0]; - Rect2 frect; - frect.position.x = c[1]; - frect.position.y = c[2]; - frect.size.x = c[3]; - frect.size.y = c[4]; - Point2 align(c[6], c[5]); - int advance = c[7]; - - font->bitmap_add_char(chr, 0, frect, align, advance); - } - - for (int i = 0; i < p_kerning_count; i++) { - font->bitmap_add_kerning_pair(p_kernings[i * 3 + 0], p_kernings[i * 3 + 1], p_kernings[i * 3 + 2]); - } - - return font; -} - static Ref<StyleBox> make_empty_stylebox(float p_margin_left = -1, float p_margin_top = -1, float p_margin_right = -1, float p_margin_bottom = -1) { Ref<StyleBox> style(memnew(StyleBoxEmpty)); @@ -425,6 +390,15 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("shadow_outline_size", "Label", 1 * scale); theme->set_constant("line_spacing", "Label", 3 * scale); + theme->set_type_variation("HeaderSmall", "Label"); + theme->set_font_size("font_size", "HeaderSmall", default_font_size + 4); + + theme->set_type_variation("HeaderMedium", "Label"); + theme->set_font_size("font_size", "HeaderMedium", default_font_size + 8); + + theme->set_type_variation("HeaderLarge", "Label"); + theme->set_font_size("font_size", "HeaderLarge", default_font_size + 12); + // LineEdit theme->set_stylebox("normal", "LineEdit", make_stylebox(line_edit_png, 5, 5, 5, 5)); @@ -438,7 +412,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_selected_color", "LineEdit", Color(0, 0, 0)); theme->set_color("font_uneditable_color", "LineEdit", Color(control_font_color.r, control_font_color.g, control_font_color.b, 0.5f)); theme->set_color("font_outline_color", "LineEdit", Color(1, 1, 1)); - theme->set_color("cursor_color", "LineEdit", control_font_hover_color); + theme->set_color("caret_color", "LineEdit", control_font_hover_color); theme->set_color("selection_color", "LineEdit", control_selection_color); theme->set_color("clear_button_color", "LineEdit", control_font_color); theme->set_color("clear_button_color_pressed", "LineEdit", control_font_pressed_color); @@ -467,7 +441,6 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("normal", "TextEdit", make_stylebox(tree_bg_png, 3, 3, 3, 3, 0, 0, 0, 0)); theme->set_stylebox("focus", "TextEdit", focus); theme->set_stylebox("read_only", "TextEdit", make_stylebox(tree_bg_disabled_png, 4, 4, 4, 4, 0, 0, 0, 0)); - theme->set_stylebox("completion", "TextEdit", make_stylebox(tree_bg_png, 3, 3, 3, 3, 0, 0, 0, 0)); theme->set_icon("tab", "TextEdit", make_icon(tab_png)); theme->set_icon("space", "TextEdit", make_icon(space_png)); @@ -476,27 +449,17 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_font_size("font_size", "TextEdit", -1); theme->set_color("background_color", "TextEdit", Color(0, 0, 0, 0)); - theme->set_color("completion_background_color", "TextEdit", Color(0.17, 0.16, 0.2)); - theme->set_color("completion_selected_color", "TextEdit", Color(0.26, 0.26, 0.27)); - theme->set_color("completion_existing_color", "TextEdit", Color(0.87, 0.87, 0.87, 0.13)); - theme->set_color("completion_scroll_color", "TextEdit", control_font_pressed_color); - theme->set_color("completion_font_color", "TextEdit", Color(0.67, 0.67, 0.67)); theme->set_color("font_color", "TextEdit", control_font_color); theme->set_color("font_selected_color", "TextEdit", Color(0, 0, 0)); theme->set_color("font_readonly_color", "TextEdit", Color(control_font_color.r, control_font_color.g, control_font_color.b, 0.5f)); theme->set_color("font_outline_color", "TextEdit", Color(1, 1, 1)); theme->set_color("selection_color", "TextEdit", control_selection_color); - theme->set_color("mark_color", "TextEdit", Color(1.0, 0.4, 0.4, 0.4)); - theme->set_color("code_folding_color", "TextEdit", Color(0.8, 0.8, 0.8, 0.8)); theme->set_color("current_line_color", "TextEdit", Color(0.25, 0.25, 0.26, 0.8)); theme->set_color("caret_color", "TextEdit", control_font_color); theme->set_color("caret_background_color", "TextEdit", Color(0, 0, 0)); theme->set_color("brace_mismatch_color", "TextEdit", Color(1, 0.2, 0.2)); theme->set_color("word_highlighted_color", "TextEdit", Color(0.8, 0.9, 0.9, 0.15)); - theme->set_constant("completion_lines", "TextEdit", 7); - theme->set_constant("completion_max_width", "TextEdit", 50); - theme->set_constant("completion_scroll_width", "TextEdit", 3); theme->set_constant("line_spacing", "TextEdit", 4 * scale); theme->set_constant("outline_size", "TextEdit", 0); @@ -514,6 +477,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("executing_line", "CodeEdit", make_icon(arrow_right_png)); theme->set_icon("can_fold", "CodeEdit", make_icon(arrow_down_png)); theme->set_icon("folded", "CodeEdit", make_icon(arrow_right_png)); + theme->set_icon("folded_eol_icon", "CodeEdit", make_icon(ellipsis_png)); theme->set_font("font", "CodeEdit", Ref<Font>()); theme->set_font_size("font_size", "CodeEdit", -1); @@ -529,12 +493,11 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_readonly_color", "CodeEdit", Color(control_font_color.r, control_font_color.g, control_font_color.b, 0.5f)); theme->set_color("font_outline_color", "CodeEdit", Color(1, 1, 1)); theme->set_color("selection_color", "CodeEdit", control_selection_color); - theme->set_color("mark_color", "CodeEdit", Color(1.0, 0.4, 0.4, 0.4)); theme->set_color("bookmark_color", "CodeEdit", Color(0.5, 0.64, 1, 0.8)); theme->set_color("breakpoint_color", "CodeEdit", Color(0.9, 0.29, 0.3)); theme->set_color("executing_line_color", "CodeEdit", Color(0.98, 0.89, 0.27)); - theme->set_color("code_folding_color", "CodeEdit", Color(0.8, 0.8, 0.8, 0.8)); theme->set_color("current_line_color", "CodeEdit", Color(0.25, 0.25, 0.26, 0.8)); + theme->set_color("code_folding_color", "CodeEdit", Color(0.8, 0.8, 0.8, 0.8)); theme->set_color("caret_color", "CodeEdit", control_font_color); theme->set_color("caret_background_color", "CodeEdit", Color(0, 0, 0)); theme->set_color("brace_mismatch_color", "CodeEdit", Color(1, 0.2, 0.2)); @@ -605,7 +568,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // ScrollContainer Ref<StyleBoxEmpty> empty; - empty.instance(); + empty.instantiate(); theme->set_stylebox("bg", "ScrollContainer", empty); // WindowDialog @@ -756,6 +719,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("guide_color", "Tree", Color(0, 0, 0, 0.1)); theme->set_color("drop_position_color", "Tree", Color(1, 0.3, 0.2)); theme->set_color("relationship_line_color", "Tree", Color(0.27, 0.27, 0.27)); + theme->set_color("parent_hl_line_color", "Tree", Color(0.27, 0.27, 0.27)); + theme->set_color("children_hl_line_color", "Tree", Color(0.27, 0.27, 0.27)); theme->set_color("custom_button_font_highlight", "Tree", control_font_hover_color); theme->set_constant("hseparation", "Tree", 4 * scale); @@ -763,6 +728,10 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("item_margin", "Tree", 12 * scale); theme->set_constant("button_margin", "Tree", 4 * scale); theme->set_constant("draw_relationship_lines", "Tree", 0); + theme->set_constant("relationship_line_width", "Tree", 1); + theme->set_constant("parent_hl_line_width", "Tree", 1); + theme->set_constant("children_hl_line_width", "Tree", 1); + theme->set_constant("parent_hl_line_margin", "Tree", 0); theme->set_constant("draw_guides", "Tree", 1); theme->set_constant("scroll_border", "Tree", 4); theme->set_constant("scroll_speed", "Tree", 12); @@ -830,7 +799,6 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("tab_selected", "Tabs", sb_expand(make_stylebox(tab_current_png, 4, 3, 4, 1, 16, 3, 16, 2), 2, 2, 2, 2)); theme->set_stylebox("tab_unselected", "Tabs", sb_expand(make_stylebox(tab_behind_png, 5, 4, 5, 1, 16, 5, 16, 2), 3, 3, 3, 3)); theme->set_stylebox("tab_disabled", "Tabs", sb_expand(make_stylebox(tab_disabled_png, 5, 5, 5, 1, 16, 6, 16, 4), 3, 0, 3, 3)); - theme->set_stylebox("panel", "Tabs", tc_sb); theme->set_stylebox("button_pressed", "Tabs", make_stylebox(button_pressed_png, 4, 4, 4, 4)); theme->set_stylebox("button", "Tabs", make_stylebox(button_normal_png, 4, 4, 4, 4)); @@ -891,6 +859,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("preset_bg", "ColorPicker", make_icon(mini_checkerboard_png)); theme->set_icon("overbright_indicator", "ColorPicker", make_icon(overbright_indicator_png)); theme->set_icon("bar_arrow", "ColorPicker", make_icon(bar_arrow_png)); + theme->set_icon("picker_cursor", "ColorPicker", make_icon(picker_cursor_png)); theme->set_icon("bg", "ColorPickerButton", make_icon(mini_checkerboard_png)); @@ -1019,23 +988,30 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const void make_default_theme(bool p_hidpi, Ref<Font> p_font) { Ref<Theme> t; - t.instance(); + t.instantiate(); Ref<StyleBox> default_style; Ref<Texture2D> default_icon; Ref<Font> default_font; - int default_font_size = 14; + if (p_font.is_valid()) { + // Use the custom font defined in the Project Settings. default_font = p_font; - } else if (p_hidpi) { - Ref<FontData> font_data = make_font(_hidpi_font_height, _hidpi_font_ascent, _hidpi_font_charcount, &_hidpi_font_charrects[0][0], _hidpi_font_kerning_pair_count, &_hidpi_font_kerning_pairs[0][0], _hidpi_font_img_width, _hidpi_font_img_height, _hidpi_font_img_data); - default_font.instance(); - default_font->add_data(font_data); } else { - Ref<FontData> font_data = make_font(_lodpi_font_height, _lodpi_font_ascent, _lodpi_font_charcount, &_lodpi_font_charrects[0][0], _lodpi_font_kerning_pair_count, &_lodpi_font_kerning_pairs[0][0], _lodpi_font_img_width, _lodpi_font_img_height, _lodpi_font_img_data); - default_font.instance(); - default_font->add_data(font_data); + // Use the default DynamicFont (separate from the editor font). + // The default DynamicFont is chosen to have a small file size since it's + // embedded in both editor and export template binaries. + Ref<Font> dynamic_font; + dynamic_font.instantiate(); + + Ref<FontData> dynamic_font_data; + dynamic_font_data.instantiate(); + dynamic_font_data->load_memory(_font_OpenSans_SemiBold, _font_OpenSans_SemiBold_size, "ttf", default_font_size); + dynamic_font->add_data(dynamic_font_data); + + default_font = dynamic_font; } + Ref<Font> large_font = default_font; fill_default_theme(t, default_font, large_font, default_icon, default_style, p_hidpi ? 2.0 : 1.0); diff --git a/scene/resources/default_theme/default_theme.h b/scene/resources/default_theme/default_theme.h index a7b2bec5a4..4cd781e814 100644 --- a/scene/resources/default_theme/default_theme.h +++ b/scene/resources/default_theme/default_theme.h @@ -33,6 +33,8 @@ #include "scene/resources/theme.h" +const int default_font_size = 16; + void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const Ref<Font> &large_font, Ref<Texture2D> &default_icon, Ref<StyleBox> &default_style, float p_scale); void make_default_theme(bool p_hidpi, Ref<Font> p_font); void clear_default_theme(); diff --git a/scene/resources/default_theme/default_theme_builders.py b/scene/resources/default_theme/default_theme_builders.py new file mode 100644 index 0000000000..0455d6d246 --- /dev/null +++ b/scene/resources/default_theme/default_theme_builders.py @@ -0,0 +1,40 @@ +"""Functions used to generate source files during build time + +All such functions are invoked in a subprocess on Windows to prevent build flakiness. + +""" +import os +import os.path +from platform_methods import subprocess_main + + +def make_fonts_header(target, source, env): + dst = target[0] + + g = open(dst, "w", encoding="utf-8") + + g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") + g.write("#ifndef _DEFAULT_FONTS_H\n") + g.write("#define _DEFAULT_FONTS_H\n") + + # Saving uncompressed, since FreeType will reference from memory pointer. + for i in range(len(source)): + with open(source[i], "rb") as f: + buf = f.read() + + name = os.path.splitext(os.path.basename(source[i]))[0] + + g.write("static const int _font_" + name + "_size = " + str(len(buf)) + ";\n") + g.write("static const unsigned char _font_" + name + "[] = {\n") + for j in range(len(buf)): + g.write("\t" + str(buf[j]) + ",\n") + + g.write("};\n") + + g.write("#endif") + + g.close() + + +if __name__ == "__main__": + subprocess_main(globals()) diff --git a/scene/resources/default_theme/ellipsis.png b/scene/resources/default_theme/ellipsis.png Binary files differnew file mode 100644 index 0000000000..c949e2c95b --- /dev/null +++ b/scene/resources/default_theme/ellipsis.png diff --git a/scene/resources/default_theme/font_hidpi.inc b/scene/resources/default_theme/font_hidpi.inc deleted file mode 100644 index 4860149e6b..0000000000 --- a/scene/resources/default_theme/font_hidpi.inc +++ /dev/null @@ -1,25463 +0,0 @@ -/* clang-format off */ -static const int _hidpi_font_height=25; -static const int _hidpi_font_ascent=19; -static const int _hidpi_font_charcount=191; -static const int _hidpi_font_charrects[191][8]={ -/* charidx , ofs_x, ofs_y, size_x, size_y, valign, halign, advance */ -{192,63,23,16,23,-4,-1,15}, -{224,184,182,10,19,0,1,13}, -{64,98,2,18,19,2,1,21}, -{96,159,234,5,4,0,5,14}, -{160,0,0,0,0,19,0,6}, -{32,0,0,0,0,19,0,6}, -{33,2,249,3,17,2,2,6}, -{193,83,25,16,23,-4,-1,15}, -{225,92,160,10,19,0,1,13}, -{65,163,23,16,17,2,-1,15}, -{161,246,236,3,17,6,2,6}, -{97,142,190,10,13,6,1,13}, -{162,67,214,9,17,2,2,13}, -{98,242,136,11,18,1,2,14}, -{194,103,25,16,23,-4,-1,15}, -{226,106,160,10,19,0,1,13}, -{66,122,146,11,17,2,2,15}, -{34,226,203,8,6,2,1,10}, -{35,189,44,14,17,2,1,16}, -{163,62,166,11,17,2,1,13}, -{195,123,23,16,23,-4,-1,15}, -{227,134,167,10,19,0,1,13}, -{67,209,65,12,17,2,1,14}, -{99,225,186,9,13,6,1,11}, -{228,170,182,10,18,1,1,13}, -{100,227,143,11,18,1,1,14}, -{196,23,23,16,22,-3,-1,15}, -{36,2,179,10,20,1,2,13}, -{68,193,65,12,17,2,2,16}, -{164,176,65,13,12,5,0,13}, -{37,120,2,18,17,2,1,20}, -{69,41,222,9,17,2,2,13}, -{165,98,99,12,17,2,1,13}, -{197,43,23,16,21,-2,-1,15}, -{229,16,179,10,20,-1,1,13}, -{101,189,109,11,13,6,1,13}, -{38,183,23,16,17,2,1,17}, -{70,54,222,9,17,2,2,12}, -{198,75,2,19,17,2,-1,20}, -{102,202,212,8,18,1,1,8}, -{166,44,268,2,24,1,6,13}, -{230,142,2,18,13,6,1,20}, -{71,2,71,14,17,2,1,17}, -{167,176,160,10,18,1,0,12}, -{199,146,94,12,23,2,1,14}, -{103,159,67,13,19,6,0,13}, -{231,212,189,9,19,6,1,11}, -{39,30,271,3,6,2,1,5}, -{72,130,99,12,17,2,2,17}, -{104,162,160,10,18,1,2,14}, -{200,238,186,9,23,-4,2,13}, -{40,46,243,7,21,2,1,7}, -{232,2,156,11,19,0,1,13}, -{168,68,235,7,3,1,4,14}, -{73,106,209,8,17,2,0,9}, -{169,230,2,17,17,2,1,20}, -{105,222,232,4,18,1,1,6}, -{201,28,222,9,23,-4,2,13}, -{41,130,238,6,21,2,0,7}, -{233,197,126,11,19,0,1,13}, -{202,93,209,9,23,-4,2,13}, -{74,13,225,7,22,2,-2,7}, -{234,167,137,11,19,0,1,13}, -{42,82,120,12,11,1,0,13}, -{170,100,236,6,8,2,1,8}, -{106,110,237,6,24,1,-1,6}, -{171,144,121,11,10,8,0,12}, -{43,2,119,12,12,5,1,13}, -{203,80,209,9,22,-3,2,13}, -{107,32,177,11,18,1,2,12}, -{235,77,166,11,18,1,1,13}, -{75,74,77,13,17,2,2,14}, -{44,230,213,4,6,16,1,6}, -{172,204,109,11,7,10,1,13}, -{236,204,234,5,19,0,0,6}, -{204,130,211,8,23,-4,0,9}, -{108,23,271,3,18,1,2,6}, -{76,86,188,10,17,2,2,12}, -{173,140,238,6,2,12,1,8}, -{45,120,237,6,2,12,1,8}, -{109,208,2,18,13,6,2,22}, -{205,178,205,8,23,-4,0,9}, -{237,213,234,5,19,0,2,6}, -{77,143,19,16,17,2,2,21}, -{46,9,251,3,3,16,2,6}, -{110,100,183,10,13,6,2,14}, -{206,154,207,8,23,-4,0,9}, -{238,238,213,8,19,0,-1,6}, -{174,2,23,17,17,2,1,20}, -{78,91,78,13,17,2,2,18}, -{175,18,125,12,2,-1,0,12}, -{111,162,90,12,13,6,1,14}, -{207,118,211,8,22,-3,0,9}, -{239,24,249,7,18,1,0,6}, -{79,203,23,15,17,2,1,18}, -{47,120,167,10,17,2,-1,9}, -{176,166,207,8,8,2,1,10}, -{112,212,143,11,19,6,2,14}, -{240,18,103,12,18,1,1,14}, -{208,171,44,14,17,2,0,16}, -{80,128,190,10,17,2,2,14}, -{48,234,115,11,17,2,1,13}, -{177,66,127,12,14,5,1,13}, -{113,182,126,11,19,6,1,14}, -{241,30,199,10,19,0,2,14}, -{81,97,52,15,22,2,1,18}, -{209,142,67,13,23,-4,2,18}, -{49,57,243,7,17,2,2,13}, -{178,190,206,8,10,2,0,8}, -{114,142,207,8,13,6,2,10}, -{242,242,87,12,19,0,1,14}, -{210,59,50,15,23,-4,1,18}, -{82,82,99,12,17,2,2,14}, -{50,2,135,11,17,2,1,13}, -{179,35,249,7,10,2,0,8}, -{115,72,188,10,13,6,0,11}, -{243,226,92,12,19,0,1,14}, -{211,40,49,15,23,-4,1,18}, -{83,129,125,11,17,2,0,13}, -{51,17,135,11,17,2,1,13}, -{180,168,219,5,4,0,5,14}, -{116,214,212,8,16,3,0,8}, -{244,194,86,12,19,0,1,14}, -{212,21,49,15,23,-4,1,18}, -{84,66,106,12,17,2,0,13}, -{52,125,78,13,17,2,0,13}, -{53,32,156,11,17,2,1,13}, -{85,50,106,12,17,2,2,17}, -{181,204,166,10,19,6,2,14}, -{117,198,189,10,13,6,2,14}, -{245,178,86,12,19,0,1,14}, -{213,2,44,15,23,-4,1,18}, -{54,47,156,11,17,2,1,13}, -{86,222,23,15,17,2,-1,14}, -{246,241,65,12,18,1,1,14}, -{214,116,52,15,22,-3,1,18}, -{182,34,131,12,21,1,1,16}, -{118,135,50,14,13,6,-1,12}, -{55,62,145,11,17,2,1,13}, -{87,2,2,22,17,2,-1,21}, -{119,28,2,20,13,6,-1,18}, -{215,114,125,11,11,5,1,13}, -{247,210,92,12,10,6,0,13}, -{183,16,251,3,3,9,2,6}, -{56,77,145,11,17,2,1,13}, -{88,225,44,14,17,2,-1,13}, -{216,78,52,15,19,1,1,18}, -{248,98,120,12,15,5,1,14}, -{120,50,127,12,13,6,0,12}, -{184,150,234,5,6,19,0,5}, -{89,207,44,14,17,2,-1,13}, -{121,153,44,14,19,6,-1,12}, -{217,225,65,12,23,-4,2,17}, -{249,44,199,10,19,0,2,14}, -{57,92,139,11,17,2,1,13}, -{185,177,232,5,10,2,0,8}, -{218,2,92,12,23,-4,2,17}, -{250,156,184,10,19,0,2,14}, -{90,232,165,10,17,2,1,13}, -{122,148,167,10,13,6,1,11}, -{58,37,263,3,13,6,2,6}, -{186,90,236,6,8,2,1,8}, -{219,34,104,12,23,-4,2,17}, -{123,2,224,7,21,2,1,9}, -{91,186,232,5,21,2,2,7}, -{251,58,191,10,19,0,2,14}, -{59,238,236,4,16,6,1,6}, -{187,47,177,11,10,8,1,12}, -{188,164,2,18,17,2,0,18}, -{252,190,149,10,18,1,2,14}, -{124,50,268,2,24,1,6,13}, -{220,114,99,12,22,-3,2,17}, -{92,137,146,11,17,2,-1,9}, -{60,159,121,11,12,5,1,13}, -{189,186,2,18,17,2,0,18}, -{253,56,77,14,25,0,-1,12}, -{221,20,76,14,23,-4,-1,13}, -{125,79,235,7,21,2,1,9}, -{93,195,234,5,21,2,0,7}, -{61,152,137,11,6,8,1,13}, -{190,52,2,19,17,2,0,18}, -{222,114,188,10,17,2,2,14}, -{254,219,115,11,24,1,2,14}, -{62,174,109,11,12,5,1,13}, -{94,108,78,13,11,2,0,13}, -{126,107,140,11,5,8,1,13}, -{223,17,156,11,18,1,2,14}, -{191,15,203,9,18,6,0,10}, -{255,38,76,14,24,1,-1,12}, -{63,2,203,9,17,2,0,10}, -{95,218,166,10,2,21,0,10}, -}; -static const int _hidpi_font_kerning_pair_count=0; -static const int _hidpi_font_kerning_pairs[1][3]={ -{0,0,0} -}; -static const int _hidpi_font_img_width=256; -static const int _hidpi_font_img_height=512; -static const int _hidpi_font_img_data_size=25255; -static const unsigned char _hidpi_font_img_data[25255]={ -137, -80, -78, -71, -13, -10, -26, -10, -0, -0, -0, -13, -73, -72, -68, -82, -0, -0, -1, -0, -0, -0, -2, -0, -8, -6, -0, -0, -0, -109, -154, -178, -251, -0, -0, -32, -0, -73, -68, -65, -84, -120, -156, -236, -157, -119, -184, -38, -53, -245, -199, -51, -84, -165, -55, -1, -21, -16, -41, -34, -42, -130, -98, -23, -1, -21, -20, -84, -108, -40, -138, -138, -10, -34, -40, -86, -148, -159, -216, -11, -34, -138, -130, -162, -40, -32, -160, -128, -160, -2, -162, -32, -162, -34, -34, -29, -145, -142, -235, -210, -219, -210, -151, -14, -219, -251, -231, -247, -199, -57, -97, -114, -231, -205, -204, -100, -102, -50, -239, -189, -119, -111, -190, -207, -179, -207, -221, -55, -57, -57, -147, -153, -73, -206, -36, -39, -167, -24, -147, -144, -144, -144, -144, -144, -144, -48, -129, -1, -172, -75, -142, -45, -74, -104, -158, -231, -208, -188, -176, -132, -230, -85, -14, -205, -58, -158, -250, -245, -156, -250, -103, -121, -234, -95, -237, -212, -239, -225, -169, -95, -25, -88, -160, -245, -235, -122, -234, -55, -5, -78, -0, -238, -5, -230, -2, -83, -128, -95, -2, -107, -149, -244, -119, -105, -224, -22, -224, -65, -96, -37, -31, -77, -8, -128, -143, -211, -0, -129, -60, -215, -1, -190, -8, -156, -15, -220, -13, -204, -1, -30, -7, -38, -1, -63, -3, -182, -12, -228, -243, -33, -224, -102, -109, -255, -63, -224, -109, -21, -180, -235, -3, -179, -244, -249, -173, -88, -195, -119, -109, -224, -43, -192, -101, -192, -19, -250, -188, -239, -5, -126, -3, -108, -160, -52, -27, -0, -191, -7, -30, -86, -190, -215, -0, -159, -0, -150, -168, -224, -187, -42, -112, -172, -62, -170, -211, -42, -232, -26, -189, -187, -62, -248, -42, -175, -235, -244, -255, -239, -211, -103, -49, -67, -223, -211, -133, -246, -89, -3, -75, -0, -159, -5, -174, -211, -231, -240, -36, -240, -79, -224, -117, -117, -253, -214, -246, -43, -105, -251, -127, -1, -143, -0, -243, -245, -239, -185, -192, -103, -234, -222, -85, -16, -128, -27, -244, -134, -246, -45, -169, -255, -164, -51, -134, -247, -41, -161, -249, -138, -214, -79, -174, -184, -206, -205, -74, -243, -126, -79, -221, -33, -206, -53, -78, -247, -212, -239, -164, -117, -55, -150, -240, -254, -43, -48, -93, -7, -225, -87, -129, -95, -1, -139, -128, -127, -149, -208, -127, -65, -249, -125, -188, -172, -191, -33, -0, -174, -5, -22, -2, -151, -56, -253, -191, -86, -127, -15, -252, -171, -225, -245, -52, -224, -251, -192, -108, -229, -115, -43, -112, -6, -112, -52, -112, -18, -112, -149, -94, -11, -224, -52, -96, -149, -10, -94, -175, -215, -251, -183, -130, -240, -126, -29, -60, -94, -225, -1, -156, -165, -124, -119, -14, -184, -231, -43, -149, -118, -1, -112, -35, -112, -5, -50, -209, -1, -30, -211, -107, -63, -166, -191, -239, -214, -241, -101, -251, -125, -98, -9, -207, -247, -2, -83, -129, -187, -128, -153, -84, -79, -212, -224, -119, -215, -35, -95, -144, -9, -253, -85, -253, -255, -125, -250, -126, -158, -212, -223, -139, -128, -157, -245, -61, -161, -215, -191, -70, -251, -128, -190, -139, -87, -215, -92, -99, -39, -224, -81, -100, -194, -31, -13, -236, -14, -188, -93, -255, -30, -173, -229, -143, -80, -33, -216, -131, -0, -28, -166, -157, -250, -75, -73, -253, -105, -136, -148, -159, -11, -156, -82, -66, -115, -142, -242, -56, -164, -226, -58, -63, -87, -154, -35, -60, -117, -183, -235, -96, -189, -18, -145, -164, -79, -43, -212, -31, -170, -109, -127, -90, -194, -123, -85, -96, -181, -66, -217, -121, -192, -116, -15, -237, -26, -136, -164, -254, -47, -21, -95, -164, -58, -0, -175, -209, -62, -253, -85, -127, -91, -120, -87, -82, -53, -188, -214, -0, -254, -173, -237, -79, -1, -94, -80, -66, -183, -46, -240, -107, -165, -187, -17, -88, -181, -132, -238, -79, -74, -179, -137, -254, -126, -169, -254, -62, -201, -67, -251, -110, -247, -62, -2, -250, -250, -38, -224, -115, -238, -243, -70, -190, -158, -71, -58, -131, -127, -33, -176, -139, -83, -255, -34, -100, -34, -2, -188, -215, -195, -243, -64, -224, -183, -192, -234, -84, -76, -212, -166, -239, -174, -71, -190, -22, -243, -129, -15, -58, -229, -203, -33, -31, -35, -128, -121, -200, -234, -235, -237, -133, -235, -92, -163, -245, -103, -84, -240, -255, -178, -62, -195, -31, -0, -43, -148, -208, -172, -0, -28, -172, -116, -95, -174, -235, -115, -213, -205, -236, -168, -29, -122, -18, -88, -178, -80, -183, -4, -34, -133, -174, -212, -127, -15, -120, -218, -47, -67, -46, -217, -182, -171, -184, -206, -59, -148, -102, -114, -161, -124, -11, -45, -63, -94, -111, -24, -224, -45, -5, -154, -171, -181, -188, -86, -218, -33, -95, -210, -215, -35, -203, -211, -223, -122, -234, -127, -161, -188, -222, -80, -199, -171, -230, -58, -191, -85, -62, -239, -208, -223, -22, -141, -4, -0, -176, -20, -178, -66, -152, -15, -236, -22, -216, -102, -95, -189, -214, -159, -75, -234, -239, -5, -110, -45, -148, -221, -9, -220, -89, -40, -91, -1, -184, -7, -249, -154, -61, -183, -73, -191, -61, -215, -92, -203, -121, -6, -199, -121, -234, -247, -215, -186, -127, -212, -240, -129, -242, -137, -218, -250, -221, -197, -228, -235, -220, -231, -209, -158, -186, -45, -156, -250, -67, -61, -245, -239, -209, -186, -7, -75, -120, -239, -132, -76, -234, -93, -245, -247, -18, -192, -222, -200, -252, -155, -137, -204, -199, -75, -128, -79, -107, -253, -110, -74, -223, -110, -37, -128, -72, -173, -57, -218, -169, -87, -20, -234, -236, -151, -227, -112, -231, -33, -109, -82, -160, -217, -74, -203, -103, -0, -203, -86, -92, -103, -37, -29, -228, -139, -128, -213, -157, -242, -3, -180, -253, -135, -145, -175, -11, -192, -145, -78, -253, -202, -122, -131, -243, -40, -145, -134, -14, -173, -139, -127, -22, -251, -3, -188, -0, -89, -186, -122, -39, -78, -40, -128, -103, -32, -43, -162, -251, -80, -161, -233, -92, -183, -169, -0, -248, -134, -182, -251, -124, -195, -118, -118, -121, -249, -90, -79, -221, -60, -224, -226, -66, -217, -165, -192, -220, -66, -217, -143, -149, -199, -87, -155, -92, -187, -162, -79, -115, -149, -223, -174, -158, -186, -55, -106, -221, -35, -53, -60, -192, -51, -81, -187, -190, -187, -152, -124, -157, -119, -61, -48, -233, -128, -167, -59, -245, -59, -120, -234, -55, -211, -186, -5, -37, -109, -31, -6, -14, -114, -202, -78, -82, -250, -169, -250, -255, -211, -149, -230, -215, -14, -205, -193, -200, -118, -160, -157, -78, -0, -81, -42, -0, -236, -95, -40, -255, -63, -251, -66, -129, -15, -234, -255, -247, -46, -208, -216, -1, -124, -86, -192, -117, -46, -85, -218, -119, -56, -101, -215, -35, -66, -97, -45, -125, -0, -179, -129, -123, -157, -122, -187, -255, -191, -48, -128, -255, -14, -200, -222, -235, -8, -109, -115, -80, -161, -254, -108, -100, -114, -108, -92, -199, -171, -230, -58, -86, -231, -241, -93, -167, -204, -34, -88, -0, -32, -66, -241, -9, -100, -133, -147, -57, -229, -203, -34, -203, -192, -201, -136, -112, -158, -141, -124, -193, -207, -36, -95, -214, -219, -47, -141, -111, -75, -53, -157, -130, -206, -1, -217, -98, -76, -115, -126, -111, -142, -12, -252, -27, -128, -165, -27, -244, -121, -73, -68, -241, -117, -18, -162, -220, -186, -31, -152, -166, -207, -53, -4, -11, -107, -248, -131, -127, -162, -118, -122, -119, -49, -249, -58, -247, -178, -89, -77, -253, -139, -61, -117, -27, -217, -74, -79, -221, -199, -16, -37, -228, -242, -250, -251, -189, -74, -122, -5, -35, -63, -154, -203, -0, -43, -59, -191, -87, -64, -4, -192, -103, -66, -239, -161, -120, -97, -59, -209, -255, -81, -40, -63, -91, -203, -159, -173, -255, -0, -126, -87, -160, -57, -79, -203, -63, -29, -112, -157, -111, -43, -237, -161, -250, -219, -158, -48, -92, -234, -208, -252, -93, -203, -94, -170, -191, -237, -254, -255, -107, -13, -239, -233, -66, -224, -9, -231, -183, -221, -234, -252, -184, -9, -31, -15, -223, -37, -16, -125, -197, -66, -224, -57, -78, -185, -69, -19, -1, -176, -151, -182, -249, -136, -83, -182, -52, -114, -2, -0, -162, -80, -60, -4, -89, -129, -221, -173, -101, -175, -117, -104, -239, -3, -174, -247, -240, -189, -137, -193, -45, -192, -20, -84, -137, -10, -100, -228, -58, -135, -109, -27, -244, -119, -13, -70, -42, -1, -255, -131, -40, -94, -127, -10, -28, -228, -60, -131, -235, -40, -81, -132, -82, -175, -12, -133, -194, -68, -141, -241, -238, -98, -242, -117, -238, -115, -163, -166, -245, -84, -11, -128, -243, -25, -185, -250, -253, -135, -146, -110, -19, -208, -167, -163, -129, -115, -155, -220, -135, -219, -120, -115, -189, -208, -12, -244, -107, -64, -190, -183, -191, -201, -161, -187, -13, -184, -207, -249, -189, -44, -185, -214, -218, -251, -48, -10, -215, -121, -173, -210, -94, -166, -191, -191, -172, -191, -247, -115, -104, -62, -165, -101, -223, -208, -223, -118, -192, -189, -162, -130, -239, -42, -133, -223, -75, -34, -95, -182, -71, -244, -247, -82, -246, -183, -75, -11, -124, -196, -55, -48, -106, -238, -225, -237, -218, -230, -239, -133, -242, -90, -120, -120, -157, -170, -85, -207, -112, -202, -172, -48, -62, -13, -88, -202, -41, -63, -89, -203, -95, -228, -148, -93, -12, -60, -238, -225, -251, -59, -165, -45, -42, -1, -79, -214, -223, -123, -235, -239, -19, -244, -119, -208, -145, -33, -162, -167, -1, -81, -150, -61, -167, -80, -151, -57, -183, -218, -90, -191, -82, -124, -31, -177, -222, -93, -13, -223, -117, -17, -1, -118, -135, -62, -131, -59, -145, -15, -79, -153, -2, -206, -34, -182, -0, -120, -20, -248, -168, -243, -219, -30, -253, -45, -89, -164, -245, -180, -221, -157, -154, -237, -85, -85, -227, -12, -120, -64, -251, -181, -149, -150, -109, -173, -191, -143, -112, -232, -236, -121, -234, -70, -250, -123, -91, -253, -125, -91, -224, -117, -150, -66, -148, -141, -115, -144, -47, -157, -61, -62, -219, -208, -161, -89, -95, -203, -46, -66, -20, -122, -243, -245, -193, -84, -157, -33, -207, -65, -142, -204, -190, -11, -124, -29, -153, -24, -0, -7, -107, -253, -167, -245, -247, -167, -156, -54, -175, -64, -246, -85, -115, -66, -7, -145, -182, -179, -171, -162, -119, -21, -202, -45, -130, -143, -1, -145, -163, -190, -187, -10, -101, -147, -148, -207, -250, -37, -215, -93, -199, -41, -59, -135, -194, -190, -94, -203, -95, -137, -124, -161, -167, -144, -31, -3, -46, -68, -236, -45, -158, -129, -28, -211, -61, -166, -255, -15, -62, -50, -212, -247, -0, -126, -77, -254, -218, -206, -51, -104, -183, -20, -53, -222, -137, -26, -229, -221, -85, -241, -5, -254, -172, -207, -224, -12, -228, -163, -100, -79, -90, -6, -148, -200, -14, -47, -136, -47, -0, -22, -48, -242, -212, -96, -62, -30, -197, -123, -201, -53, -223, -14, -204, -15, -161, -45, -99, -240, -27, -237, -151, -253, -242, -126, -71, -127, -191, -199, -161, -249, -144, -150, -125, -76, -127, -219, -37, -253, -207, -27, -92, -231, -207, -218, -230, -213, -200, -222, -107, -146, -135, -102, -178, -214, -109, -163, -180, -127, -168, -225, -121, -16, -114, -44, -54, -83, -7, -197, -100, -196, -152, -102, -73, -228, -136, -240, -17, -68, -215, -176, -148, -210, -175, -141, -104, -202, -247, -68, -20, -87, -161, -131, -104, -35, -29, -40, -247, -227, -124, -157, -181, -206, -162, -201, -22, -224, -73, -224, -170, -66, -217, -108, -224, -81, -15, -237, -85, -202, -127, -69, -167, -236, -106, -28, -125, -73, -129, -254, -29, -200, -23, -110, -174, -254, -221, -89, -203, -237, -123, -222, -75, -127, -255, -81, -127, -23, -87, -11, -3, -103, -246, -228, -202, -226, -55, -122, -234, -236, -106, -14, -224, -114, -28, -157, -70, -19, -104, -251, -211, -244, -255, -49, -223, -93, -41, -95, -96, -53, -224, -85, -5, -250, -243, -128, -89, -21, -188, -160, -255, -21, -192, -195, -200, -60, -8, -57, -154, -108, -191, -2, -80, -6, -86, -201, -247, -47, -253, -125, -1, -131, -26, -251, -117, -148, -230, -4, -135, -6, -26, -28, -65, -144, -75, -222, -163, -245, -239, -119, -60, -52, -7, -219, -235, -232, -223, -214, -6, -59, -192, -79, -148, -199, -14, -250, -123, -25, -68, -25, -249, -91, -253, -253, -212, -192, -8, -224, -101, -245, -17, -85, -104, -34, -0, -230, -1, -255, -46, -148, -61, -12, -60, -89, -40, -179, -43, -39, -236, -96, -32, -63, -189, -105, -162, -189, -182, -43, -182, -127, -163, -19, -20, -255, -145, -225, -20, -10, -71, -134, -90, -110, -183, -99, -167, -0, -203, -104, -89, -134, -28, -69, -205, -70, -116, -2, -183, -43, -205, -177, -192, -154, -78, -219, -117, -16, -163, -178, -183, -54, -232, -111, -180, -119, -87, -197, -183, -132, -230, -122, -60, -31, -39, -231, -186, -16, -95, -0, -92, -200, -72, -29, -128, -213, -135, -189, -38, -224, -158, -142, -166, -196, -240, -45, -8, -192, -154, -200, -132, -159, -142, -44, -207, -103, -162, -230, -142, -5, -186, -219, -144, -189, -210, -82, -250, -210, -231, -160, -90, -203, -192, -235, -108, -162, -55, -53, -189, -108, -194, -0, -175, -211, -186, -25, -250, -119, -253, -150, -247, -180, -49, -50, -201, -254, -238, -148, -253, -18, -217, -195, -46, -167, -191, -131, -6, -17, -114, -66, -241, -168, -62, -35, -223, -18, -223, -162, -137, -0, -152, -138, -163, -99, -209, -178, -127, -41, -159, -173, -156, -178, -143, -146, -91, -211, -109, -168, -101, -86, -87, -50, -112, -228, -86, -114, -173, -101, -144, -85, -210, -124, -28, -237, -180, -62, -31, -223, -137, -129, -111, -107, -177, -141, -211, -143, -135, -145, -21, -136, -181, -250, -187, -9, -153, -228, -47, -39, -223, -42, -44, -210, -255, -91, -59, -17, -8, -80, -22, -235, -181, -162, -189, -187, -58, -190, -30, -154, -207, -107, -223, -119, -42, -169, -183, -136, -45, -0, -246, -98, -228, -41, -128, -181, -25, -248, -15, -176, -70, -129, -118, -25, -231, -255, -246, -20, -224, -115, -229, -119, -30, -0, -114, -43, -165, -119, -234, -95, -159, -33, -131, -213, -3, -216, -115, -221, -198, -154, -71, -114, -141, -246, -192, -87, -70, -235, -151, -116, -6, -214, -45, -109, -238, -69, -249, -156, -137, -12, -248, -23, -232, -239, -79, -40, -223, -13, -28, -154, -160, -65, -4, -236, -81, -117, -191, -206, -75, -111, -34, -0, -46, -213, -254, -173, -224, -148, -189, -81, -7, -223, -147, -136, -117, -221, -73, -58, -96, -63, -141, -28, -25, -94, -139, -216, -100, -204, -210, -246, -75, -85, -93, -195, -225, -251, -53, -223, -59, -37, -224, -200, -176, -80, -183, -53, -114, -108, -252, -48, -50, -177, -111, -68, -108, -57, -220, -99, -169, -231, -32, -71, -177, -119, -34, -203, -244, -25, -218, -239, -131, -128, -181, -3, -251, -27, -237, -221, -85, -241, -245, -212, -127, -82, -235, -247, -172, -224, -97, -17, -91, -0, -44, -135, -8, -204, -239, -57, -101, -118, -203, -246, -16, -162, -8, -62, -22, -177, -113, -113, -143, -116, -127, -160, -207, -102, -229, -34, -207, -70, -64, -108, -209, -33, -215, -56, -15, -44, -215, -200, -245, -0, -182, -99, -251, -249, -120, -141, -54, -200, -5, -212, -207, -157, -178, -186, -179, -234, -245, -43, -248, -217, -229, -239, -46, -37, -245, -22, -77, -4, -192, -129, -218, -230, -125, -133, -242, -183, -34, -75, -208, -185, -136, -118, -126, -119, -45, -223, -17, -89, -98, -63, -170, -207, -127, -117, -63, -231, -129, -235, -108, -128, -8, -140, -187, -41, -104, -182, -41, -63, -50, -108, -45, -120, -187, -34, -246, -187, -171, -226, -91, -168, -255, -54, -34, -120, -223, -226, -171, -31, -6, -128, -119, -225, -152, -82, -35, -91, -172, -61, -80, -161, -140, -163, -220, -213, -250, -15, -40, -253, -59, -99, -92, -252, -245, -250, -128, -158, -64, -164, -224, -128, -101, -17, -185, -30, -224, -113, -253, -251, -34, -31, -175, -209, -4, -178, -130, -248, -175, -246, -209, -213, -97, -124, -222, -243, -15, -228, -220, -250, -243, -148, -120, -129, -33, -90, -103, -144, -229, -153, -215, -104, -198, -25, -136, -77, -4, -192, -198, -250, -66, -175, -43, -227, -27, -3, -192, -223, -180, -111, -239, -242, -212, -149, -29, -25, -54, -222, -91, -199, -64, -236, -119, -87, -199, -215, -169, -251, -37, -34, -12, -159, -223, -215, -189, -133, -2, -57, -201, -90, -8, -124, -143, -146, -237, -53, -176, -60, -242, -193, -94, -8, -124, -61, -214, -133, -151, -33, -223, -119, -95, -86, -65, -119, -155, -210, -220, -19, -229, -194, -145, -65, -110, -96, -227, -245, -112, -44, -208, -214, -14, -118, -114, -101, -228, -193, -53, -124, -160, -161, -55, -32, -185, -213, -226, -81, -116, -112, -78, -170, -232, -151, -221, -71, -122, -45, -53, -41, -63, -50, -12, -114, -89, -141, -141, -216, -239, -46, -132, -47, -114, -252, -55, -19, -248, -166, -71, -200, -120, -173, -253, -250, -6, -178, -18, -120, -28, -249, -232, -28, -137, -232, -129, -222, -174, -127, -143, -212, -242, -199, -136, -241, -229, -47, -92, -248, -47, -250, -160, -190, -87, -65, -99, -245, -0, -199, -70, -189, -120, -4, -32, -230, -181, -15, -34, -190, -221, -181, -95, -213, -186, -65, -132, -120, -146, -205, -70, -246, -229, -165, -198, -78, -4, -160, -164, -221, -178, -228, -74, -196, -243, -241, -216, -246, -59, -180, -27, -0, -95, -168, -187, -39, -135, -126, -69, -68, -203, -63, -139, -234, -237, -205, -187, -144, -189, -252, -92, -253, -235, -221, -230, -244, -141, -216, -239, -46, -148, -111, -205, -107, -251, -68, -219, -251, -233, -10, -96, -21, -228, -56, -251, -2, -100, -219, -183, -64, -255, -94, -128, -184, -47, -119, -219, -243, -39, -140, -13, -168, -16, -248, -57, -178, -237, -2, -57, -29, -248, -27, -112, -34, -162, -4, -252, -7, -98, -246, -91, -42, -72, -134, -216, -87, -23, -63, -11, -160, -255, -180, -219, -96, -24, -125, -76, -72, -24, -151, -0, -158, -139, -24, -96, -93, -128, -44, -197, -231, -32, -202, -175, -135, -16, -37, -208, -143, -40, -24, -172, -140, -66, -31, -93, -220, -75, -141, -193, -15, -35, -143, -72, -147, -0, -72, -72, -24, -207, -208, -121, -188, -8, -177, -182, -132, -10, -35, -21, -68, -97, -188, -8, -81, -212, -37, -1, -48, -70, -16, -93, -209, -148, -48, -225, -144, -25, -99, -206, -214, -255, -191, -167, -130, -238, -125, -74, -219, -41, -254, -66, -194, -144, -64, -79, -65, -8, -17, -207, -182, -219, -172, -210, -2, -177, -197, -190, -3, -245, -78, -107, -201, -115, -51, -196, -230, -124, -134, -46, -153, -91, -251, -248, -147, -219, -217, -79, -199, -241, -204, -155, -104, -32, -204, -235, -204, -194, -6, -111, -185, -171, -130, -246, -74, -29, -67, -79, -5, -141, -109, -208, -151, -23, -58, -215, -218, -60, -180, -93, -129, -199, -43, -16, -95, -135, -169, -228, -78, -101, -127, -35, -192, -189, -182, -132, -223, -106, -72, -28, -192, -75, -17, -165, -226, -60, -253, -123, -9, -240, -37, -234, -143, -34, -215, -38, -183, -164, -172, -220, -202, -57, -207, -108, -33, -129, -198, -83, -157, -64, -143, -65, -8, -201, -157, -72, -94, -171, -191, -109, -20, -161, -217, -29, -250, -123, -57, -162, -28, -219, -6, -241, -138, -187, -168, -3, -175, -231, -234, -139, -4, -79, -128, -141, -197, -9, -58, -8, -207, -67, -142, -152, -78, -67, -237, -22, -16, -123, -254, -251, -2, -218, -91, -108, -130, -24, -43, -129, -199, -85, -27, -57, -177, -0, -241, -98, -124, -81, -11, -1, -240, -67, -231, -90, -3, -22, -169, -1, -237, -183, -39, -87, -170, -62, -138, -152, -45, -91, -19, -229, -133, -148, -152, -249, -86, -240, -123, -55, -185, -237, -11, -136, -149, -227, -21, -228, -71, -226, -232, -24, -26, -112, -148, -42, -240, -185, -64, -105, -75, -143, -148, -149, -206, -250, -195, -92, -208, -164, -159, -173, -64, -207, -65, -8, -25, -92, -1, -172, -130, -172, -0, -188, -65, -70, -3, -121, -206, -68, -221, -82, -129, -93, -240, -4, -0, -109, -200, -111, -75, -196, -0, -106, -33, -240, -242, -46, -188, -10, -124, -215, -36, -183, -173, -184, -162, -3, -31, -87, -155, -30, -100, -79, -95, -194, -231, -56, -228, -88, -112, -50, -178, -63, -183, -251, -249, -89, -192, -49, -1, -237, -45, -54, -35, -247, -24, -253, -145, -135, -206, -70, -204, -253, -8, -121, -188, -137, -208, -208, -232, -75, -34, -167, -30, -211, -244, -223, -3, -4, -172, -78, -10, -60, -236, -170, -238, -120, -52, -52, -28, -114, -218, -98, -45, -88, -7, -252, -92, -42, -120, -237, -72, -30, -150, -254, -116, -10, -199, -193, -136, -233, -243, -239, -181, -126, -14, -21, -95, -119, -114, -31, -142, -74, -75, -75, -228, -200, -18, -28, -55, -232, -94, -192, -96, -16, -66, -23, -179, -16, -5, -206, -103, -28, -250, -110, -65, -8, -35, -1, -89, -94, -158, -133, -216, -166, -159, -67, -64, -200, -176, -0, -158, -91, -234, -96, -187, -33, -70, -31, -149, -231, -81, -250, -44, -31, -160, -16, -68, -163, -1, -143, -23, -33, -66, -196, -78, -136, -25, -148, -216, -178, -7, -240, -154, -10, -124, -64, -255, -255, -98, -68, -56, -63, -137, -172, -10, -188, -121, -20, -10, -237, -45, -182, -32, -143, -111, -119, -135, -135, -110, -146, -78, -134, -149, -112, -2, -101, -6, -246, -241, -205, -74, -126, -166, -254, -3, -216, -177, -225, -125, -218, -85, -231, -75, -10, -229, -91, -218, -137, -26, -200, -231, -105, -200, -105, -7, -72, -0, -151, -210, -83, -15, -228, -216, -22, -196, -124, -219, -43, -176, -24, -185, -13, -240, -90, -209, -146, -111, -127, -250, -93, -254, -227, -15, -66, -8, -178, -23, -62, -10, -248, -3, -185, -27, -234, -199, -28, -154, -110, -65, -8, -35, -0, -177, -129, -182, -152, -6, -60, -111, -180, -250, -82, -6, -196, -51, -110, -33, -178, -116, -108, -28, -46, -92, -121, -172, -133, -44, -55, -167, -33, -49, -20, -94, -167, -2, -224, -142, -144, -9, -235, -225, -23, -228, -60, -84, -209, -254, -41, -1, -160, -191, -109, -190, -135, -45, -29, -154, -77, -181, -236, -116, -253, -221, -84, -0, -216, -136, -203, -123, -34, -113, -242, -0, -126, -223, -176, -159, -214, -225, -236, -205, -133, -114, -203, -47, -200, -215, -129, -220, -77, -126, -54, -142, -123, -115, -9, -173, -13, -63, -14, -78, -220, -75, -15, -221, -133, -74, -243, -141, -146, -250, -175, -107, -125, -231, -143, -90, -37, -40, -4, -33, -212, -50, -128, -41, -206, -239, -151, -105, -217, -121, -78, -89, -227, -32, -132, -68, -80, -234, -20, -248, -117, -218, -35, -14, -3, -136, -85, -221, -84, -2, -179, -249, -148, -240, -184, -10, -49, -211, -117, -93, -120, -95, -138, -36, -155, -184, -170, -170, -109, -31, -240, -8, -0, -235, -208, -244, -125, -135, -198, -110, -13, -172, -67, -75, -176, -0, -64, -172, -23, -103, -34, -75, -238, -103, -32, -138, -183, -121, -200, -106, -52, -56, -139, -19, -121, -20, -235, -243, -200, -99, -31, -108, -71, -190, -143, -31, -240, -139, -40, -225, -99, -151, -246, -65, -39, -25, -228, -1, -86, -142, -175, -160, -177, -219, -185, -107, -75, -234, -109, -24, -252, -214, -91, -189, -32, -80, -8, -66, -168, -101, -69, -1, -176, -162, -150, -253, -175, -64, -215, -40, -8, -97, -204, -9, -75, -132, -61, -162, -135, -103, -84, -1, -53, -214, -209, -246, -126, -61, -2, -192, -78, -238, -91, -28, -154, -155, -144, -85, -202, -114, -5, -154, -16, -1, -96, -221, -174, -255, -233, -148, -89, -103, -166, -143, -85, -181, -45, -240, -121, -22, -185, -75, -249, -49, -200, -170, -98, -17, -34, -76, -62, -217, -128, -143, -93, -225, -4, -133, -78, -39, -143, -140, -52, -16, -172, -213, -161, -113, -183, -1, -235, -23, -234, -108, -26, -189, -133, -192, -51, -67, -251, -217, -10, -20, -66, -16, -105, -217, -83, -2, -0, -241, -79, -182, -46, -194, -71, -20, -232, -130, -67, -16, -197, -158, -176, -68, -216, -35, -122, -120, -142, -249, -21, -69, -76, -180, -189, -223, -162, -0, -208, -178, -91, -181, -108, -115, -103, -178, -255, -214, -169, -111, -34, -0, -172, -150, -124, -79, -167, -236, -35, -90, -214, -104, -73, -76, -158, -245, -200, -226, -82, -52, -218, -116, -3, -30, -118, -11, -252, -161, -64, -250, -221, -148, -254, -177, -26, -58, -187, -13, -216, -183, -80, -254, -89, -45, -111, -125, -170, -21, -12, -10, -65, -8, -181, -12, -68, -82, -90, -205, -181, -125, -112, -69, -23, -202, -224, -32, -132, -68, -158, -176, -68, -216, -35, -22, -248, -141, -249, -21, -69, -76, -126, -93, -238, -215, -233, -131, -43, -0, -108, -70, -167, -3, -157, -255, -239, -228, -212, -7, -9, -0, -68, -155, -110, -191, -210, -110, -234, -177, -149, -17, -165, -222, -34, -2, -163, -67, -33, -43, -9, -155, -142, -204, -226, -7, -33, -109, -11, -124, -172, -246, -191, -54, -111, -162, -210, -239, -172, -244, -149, -115, -131, -124, -27, -80, -76, -224, -114, -190, -150, -183, -14, -172, -26, -12, -202, -87, -0, -51, -16, -39, -148, -187, -245, -1, -108, -224, -105, -219, -100, -5, -16, -109, -194, -18, -105, -143, -88, -224, -57, -230, -87, -20, -49, -249, -117, -185, -95, -167, -15, -174, -0, -176, -154, -245, -155, -17, -229, -228, -99, -140, -12, -89, -21, -42, -0, -108, -146, -153, -129, -112, -93, -136, -203, -46, -192, -55, -107, -120, -44, -137, -28, -117, -218, -113, -252, -85, -36, -145, -137, -205, -90, -244, -253, -2, -189, -53, -104, -242, -70, -182, -70, -142, -134, -33, -60, -109, -91, -232, -10, -224, -153, -200, -50, -127, -33, -170, -92, -68, -242, -46, -44, -64, -4, -221, -64, -22, -237, -232, -160, -16, -132, -80, -203, -32, -223, -2, -216, -24, -248, -190, -140, -42, -65, -65, -8, -137, -60, -97, -137, -180, -71, -44, -240, -28, -211, -43, -138, -30, -248, -181, -190, -95, -114, -108, -81, -40, -191, -195, -169, -59, -182, -80, -23, -42, -0, -110, -161, -30, -183, -214, -240, -176, -123, -240, -135, -113, -210, -217, -35, -81, -150, -236, -209, -160, -171, -176, -252, -165, -150, -121, -147, -131, -32, -138, -92, -8, -215, -1, -212, -102, -202, -118, -104, -47, -82, -218, -143, -235, -239, -143, -234, -239, -139, -235, -218, -70, -1, -133, -32, -132, -90, -6, -35, -149, -128, -231, -107, -217, -235, -157, -178, -224, -32, -132, -68, -158, -176, -68, -220, -35, -106, -219, -49, -191, -162, -136, -201, -175, -235, -253, -146, -163, -40, -0, -126, -228, -212, -109, -87, -168, -171, -21, -0, -200, -17, -39, -200, -87, -187, -44, -171, -144, -13, -36, -91, -229, -128, -100, -163, -18, -15, -164, -178, -71, -12, -122, -158, -18, -2, -72, -102, -170, -57, -136, -197, -160, -215, -148, -156, -60, -55, -95, -232, -41, -128, -205, -204, -60, -144, -32, -213, -67, -251, -25, -165, -61, -189, -208, -246, -179, -33, -215, -234, -12, -252, -65, -8, -97, -164, -0, -216, -18, -89, -146, -92, -75, -30, -146, -58, -56, -8, -33, -113, -149, -58, -207, -33, -210, -30, -209, -105, -59, -30, -86, -20, -49, -183, -80, -157, -238, -151, -28, -69, -1, -96, -195, -166, -77, -101, -48, -203, -116, -136, -0, -176, -6, -83, -135, -87, -208, -252, -76, -105, -142, -170, -160, -177, -177, -3, -183, -45, -169, -127, -51, -121, -54, -43, -123, -74, -240, -195, -10, -126, -239, -83, -154, -57, -52, -179, -3, -120, -123, -21, -173, -210, -219, -109, -192, -147, -200, -92, -156, -174, -99, -248, -217, -117, -109, -163, -129, -193, -32, -132, -224, -8, -0, -45, -179, -22, -78, -31, -163, -65, -16, -66, -34, -79, -88, -34, -236, -17, -61, -237, -198, -244, -138, -162, -7, -126, -81, -239, -55, -6, -16, -19, -93, -59, -25, -95, -89, -65, -103, -29, -100, -30, -163, -36, -19, -53, -146, -238, -28, -224, -43, -21, -124, -118, -37, -199, -29, -84, -68, -31, -210, -190, -221, -165, -180, -191, -43, -163, -83, -90, -107, -102, -124, -19, -225, -74, -85, -187, -13, -248, -162, -254, -173, -204, -157, -216, -11, -232, -41, -8, -97, -236, -9, -75, -132, -61, -98, -129, -223, -115, -24, -227, -43, -138, -152, -252, -250, -184, -223, -24, -32, -143, -91, -88, -107, -157, -71, -190, -196, -247, -186, -33, -147, -39, -253, -152, -173, -207, -206, -205, -175, -184, -30, -146, -123, -241, -225, -194, -152, -57, -160, -230, -154, -111, -34, -63, -13, -56, -133, -193, -188, -136, -235, -145, -7, -87, -157, -67, -133, -16, -243, -240, -182, -219, -128, -251, -245, -111, -183, -216, -254, -109, -65, -15, -65, -8, -137, -56, -97, -137, -180, -71, -44, -240, -28, -15, -43, -138, -152, -91, -168, -232, -247, -27, -3, -228, -113, -40, -107, -175, -77, -110, -121, -120, -102, -73, -253, -74, -136, -167, -168, -197, -124, -228, -52, -203, -245, -228, -91, -136, -108, -57, -118, -116, -198, -204, -64, -134, -170, -2, -223, -119, -21, -120, -220, -166, -215, -113, -189, -1, -167, -226, -232, -202, -2, -239, -253, -89, -136, -224, -69, -255, -174, -83, -223, -170, -39, -16, -49, -8, -33, -145, -39, -44, -145, -246, -136, -5, -250, -49, -189, -162, -232, -129, -95, -212, -251, -141, -1, -100, -91, -99, -221, -118, -7, -142, -155, -61, -244, -214, -207, -96, -30, -37, -177, -27, -144, -204, -86, -159, -68, -20, -216, -54, -174, -197, -99, -136, -99, -219, -161, -56, -161, -191, -145, -109, -133, -61, -234, -251, -118, -205, -181, -215, -64, -132, -232, -101, -72, -152, -54, -27, -174, -237, -98, -100, -101, -209, -54, -94, -134, -77, -102, -251, -239, -122, -234, -113, -2, -34, -78, -88, -34, -238, -17, -29, -218, -49, -191, -162, -136, -201, -175, -143, -251, -77, -72, -240, -34, -246, -132, -37, -226, -30, -209, -161, -27, -15, -43, -138, -152, -91, -168, -232, -247, -155, -144, -224, -69, -236, -9, -75, -196, -61, -162, -210, -140, -249, -21, -69, -76, -126, -125, -220, -111, -66, -66, -41, -98, -78, -88, -250, -217, -35, -142, -249, -21, -69, -76, -126, -125, -220, -111, -194, -4, -3, -225, -231, -154, -209, -39, -108, -108, -196, -20, -80, -74, -19, -123, -203, -19, -155, -95, -212, -251, -13, -1, -162, -48, -179, -86, -119, -165, -249, -246, -144, -252, -136, -32, -182, -250, -107, -84, -208, -21, -177, -16, -57, -206, -187, -4, -177, -247, -95, -181, -97, -255, -214, -82, -62, -39, -20, -202, -47, -3, -30, -172, -104, -183, -10, -185, -210, -240, -131, -21, -116, -239, -82, -154, -39, -129, -85, -60, -245, -43, -146, -219, -44, -236, -81, -193, -103, -71, -165, -185, -135, -190, -3, -240, -208, -49, -96, -228, -120, -0, -227, -96, -69, -17, -147, -95, -31, -247, -27, -10, -242, -179, -241, -42, -75, -187, -131, -148, -230, -212, -26, -94, -22, -54, -247, -226, -127, -200, -163, -254, -160, -99, -118, -32, -155, -117, -5, -191, -237, -181, -221, -23, -157, -178, -12, -217, -86, -253, -179, -166, -237, -183, -181, -109, -105, -92, -65, -114, -205, -254, -129, -21, -52, -86, -72, -60, -130, -39, -211, -51, -18, -142, -204, -190, -223, -119, -135, -222, -91, -107, -208, -49, -96, -228, -68, -5, -241, -87, -20, -67, -255, -98, -247, -1, -36, -98, -51, -136, -243, -210, -64, -40, -50, -96, -9, -242, -120, -123, -111, -170, -225, -101, -81, -52, -65, -222, -20, -248, -179, -214, -45, -160, -196, -4, -216, -195, -239, -11, -218, -102, -123, -167, -204, -70, -50, -174, -244, -182, -100, -228, -42, -224, -205, -158, -122, -27, -69, -107, -186, -111, -98, -23, -104, -109, -223, -127, -229, -169, -179, -130, -198, -155, -216, -53, -58, -232, -24, -48, -114, -34, -130, -200, -95, -216, -216, -252, -70, -27, -136, -89, -44, -120, -108, -227, -129, -29, -180, -110, -10, -53, -153, -145, -203, -4, -128, -214, -101, -192, -201, -90, -127, -23, -97, -137, -69, -143, -87, -250, -181, -157, -178, -119, -106, -217, -71, -3, -218, -219, -201, -57, -224, -17, -75, -190, -242, -25, -136, -150, -236, -161, -93, -151, -220, -7, -96, -43, -167, -124, -67, -196, -154, -113, -38, -195, -178, -206, -164, -99, -192, -200, -132, -132, -34, -200, -191, -180, -103, -120, -234, -78, -209, -186, -111, -5, -240, -41, -21, -0, -90, -191, -6, -185, -147, -207, -7, -2, -248, -93, -13, -60, -84, -40, -179, -118, -23, -181, -81, -131, -24, -185, -10, -112, -3, -162, -174, -163, -2, -124, -22, -129, -31, -77, -96, -95, -229, -243, -63, -59, -7, -201, -205, -189, -27, -133, -224, -31, -115, -32, -82, -60, -123, -135, -95, -148, -56, -251, -9, -195, -1, -226, -33, -103, -93, -110, -215, -116, -202, -87, -213, -242, -133, -192, -122, -1, -124, -42, -5, -128, -210, -216, -128, -156, -94, -167, -29, -90, -160, -166, -79, -118, -21, -112, -170, -83, -102, -19, -122, -252, -180, -238, -158, -156, -54, -75, -2, -215, -104, -187, -47, -145, -235, -6, -38, -19, -176, -154, -137, -2, -58, -166, -61, -42, -225, -25, -45, -158, -189, -195, -179, -83, -156, -125, -82, -26, -176, -74, -32, -91, -16, -235, -82, -91, -186, -5, -1, -54, -82, -154, -218, -173, -7, -185, -59, -243, -126, -78, -153, -77, -144, -113, -118, -85, -91, -135, -222, -162, -74, -0, -236, -175, -52, -94, -227, -40, -224, -88, -253, -119, -186, -210, -93, -227, -148, -29, -139, -172, -32, -30, -118, -203, -106, -250, -100, -87, -1, -11, -144, -37, -251, -242, -200, -137, -204, -28, -26, -186, -244, -34, -122, -131, -133, -58, -71, -238, -69, -182, -4, -175, -107, -194, -163, -53, -136, -148, -246, -168, -192, -51, -106, -60, -123, -229, -25, -35, -206, -254, -132, -73, -3, -214, -22, -228, -138, -169, -82, -37, -36, -240, -93, -165, -249, -99, -0, -191, -173, -149, -246, -122, -167, -204, -10, -226, -247, -214, -180, -173, -68, -129, -214, -134, -227, -122, -162, -134, -167, -141, -219, -183, -135, -83, -182, -188, -142, -173, -63, -212, -221, -79, -129, -151, -93, -5, -28, -65, -190, -218, -109, -101, -73, -73, -110, -215, -1, -240, -235, -54, -60, -218, -92, -52, -90, -218, -163, -66, -187, -232, -241, -236, -137, -16, -103, -95, -249, -244, -146, -6, -172, -112, -141, -12, -9, -38, -81, -171, -87, -33, -242, -234, -139, -142, -201, -88, -201, -163, -233, -122, -143, -33, -245, -222, -166, -40, -77, -80, -134, -40, -242, -208, -90, -47, -37, -87, -92, -62, -140, -19, -59, -176, -164, -157, -181, -122, -180, -184, -214, -41, -187, -164, -64, -107, -39, -246, -188, -26, -158, -7, -40, -157, -187, -119, -183, -1, -77, -188, -201, -58, -42, -120, -217, -85, -192, -44, -125, -38, -243, -105, -169, -180, -3, -246, -113, -238, -179, -113, -0, -211, -54, -23, -140, -154, -246, -104, -60, -129, -142, -105, -192, -60, -147, -118, -46, -98, -172, -113, -1, -226, -137, -182, -159, -62, -175, -223, -214, -240, -9, -89, -125, -61, -68, -33, -204, -86, -13, -207, -78, -201, -88, -129, -101, -144, -179, -105, -240, -8, -124, -224, -245, -90, -231, -61, -222, -43, -225, -249, -121, -109, -115, -32, -185, -178, -205, -27, -135, -175, -164, -189, -69, -213, -22, -224, -163, -74, -83, -25, -172, -22, -9, -171, -182, -0, -120, -154, -83, -182, -167, -182, -13, -118, -119, -119, -218, -126, -219, -233, -95, -171, -47, -55, -226, -225, -249, -48, -162, -245, -159, -133, -108, -71, -106, -117, -35, -157, -64, -15, -105, -143, -38, -2, -60, -147, -246, -14, -157, -180, -247, -144, -251, -118, -131, -124, -201, -189, -249, -223, -148, -79, -211, -213, -87, -168, -87, -98, -231, -100, -172, -192, -225, -122, -221, -95, -120, -234, -236, -17, -90, -169, -129, -143, -167, -205, -106, -58, -206, -46, -35, -95, -254, -7, -235, -131, -2, -5, -128, -205, -72, -52, -201, -83, -231, -238, -245, -31, -211, -177, -236, -150, -93, -171, -109, -255, -68, -192, -254, -191, -192, -123, -21, -167, -127, -175, -14, -109, -87, -224, -113, -168, -182, -255, -46, -121, -124, -197, -147, -218, -240, -106, -114, -209, -232, -105, -143, -148, -46, -218, -146, -54, -38, -175, -24, -40, -76, -218, -211, -128, -13, -11, -245, -27, -147, -235, -24, -230, -80, -72, -78, -233, -208, -181, -89, -125, -221, -194, -144, -142, -106, -201, -141, -89, -30, -193, -209, -68, -35, -123, -101, -235, -132, -84, -106, -226, -91, -194, -243, -68, -125, -127, -139, -128, -203, -26, -182, -181, -168, -18, -0, -231, -42, -205, -128, -209, -26, -45, -16, -187, -127, -21, -109, -55, -66, -86, -144, -15, -34, -38, -194, -107, -144, -219, -6, -116, -218, -238, -214, -93, -184, -143, -180, -71, -209, -20, -138, -49, -121, -41, -191, -78, -194, -132, -145, -147, -246, -119, -120, -38, -45, -240, -33, -173, -183, -198, -60, -215, -151, -208, -181, -93, -125, -5, -229, -180, -139, -1, -228, -24, -10, -156, -21, -31, -240, -97, -45, -107, -52, -129, -181, -173, -221, -138, -64, -243, -112, -102, -22, -101, -118, -0, -27, -147, -167, -219, -42, -53, -9, -38, -87, -72, -126, -163, -80, -254, -16, -112, -121, -147, -62, -53, -233, -95, -77, -91, -27, -219, -97, -31, -167, -236, -123, -90, -118, -126, -219, -62, -133, -92, -56, -106, -218, -35, -226, -230, -81, -143, -170, -156, -36, -130, -48, -33, -159, -180, -179, -240, -56, -174, -32, -146, -251, -97, -29, -136, -111, -35, -159, -180, -3, -138, -50, -198, -199, -234, -235, -255, -244, -154, -238, -57, -183, -253, -202, -238, -21, -202, -199, -105, -187, -190, -182, -157, -14, -172, -208, -176, -173, -133, -207, -18, -240, -233, -228, -138, -194, -201, -84, -175, -166, -62, -169, -116, -174, -80, -123, -166, -150, -5, -47, -251, -155, -244, -175, -166, -157, -213, -167, -220, -204, -200, -248, -133, -171, -146, -27, -26, -213, -70, -24, -110, -5, -34, -166, -61, -34, -162, -66, -49, -38, -47, -165, -137, -34, -76, -28, -26, -239, -209, -23, -121, -84, -216, -159, -233, -111, -27, -231, -221, -183, -36, -109, -187, -250, -42, -85, -90, -18, -127, -197, -244, -76, -125, -110, -51, -145, -176, -213, -107, -33, -194, -173, -109, -68, -98, -155, -246, -186, -241, -68, -115, -250, -239, -102, -36, -90, -2, -216, -22, -184, -82, -235, -102, -81, -51, -1, -129, -159, -43, -237, -115, -157, -50, -155, -29, -168, -117, -64, -78, -95, -255, -2, -218, -44, -129, -132, -41, -3, -143, -179, -15, -240, -45, -173, -187, -145, -62, -182, -126, -68, -76, -123, -68, -68, -133, -98, -100, -94, -49, -5, -147, -157, -180, -251, -123, -234, -172, -119, -217, -221, -232, -215, -141, -138, -44, -49, -180, -95, -125, -61, -94, -82, -223, -215, -113, -174, -53, -73, -221, -25, -216, -91, -255, -127, -98, -72, -219, -2, -159, -165, -16, -129, -4, -240, -178, -22, -237, -45, -236, -49, -224, -181, -228, -46, -211, -32, -199, -203, -175, -8, -224, -115, -62, -98, -155, -146, -57, -101, -246, -196, -230, -13, -77, -251, -229, -233, -95, -19, -1, -176, -151, -182, -185, -180, -164, -126, -37, -36, -38, -39, -120, -18, -157, -116, -6, -17, -211, -30, -17, -113, -73, -27, -153, -87, -76, -97, -226, -157, -180, -200, -215, -209, -166, -198, -122, -171, -83, -110, -245, -1, -143, -122, -120, -181, -93, -125, -45, -240, -212, -245, -118, -156, -75, -158, -24, -227, -120, -224, -239, -250, -255, -70, -19, -5, -57, -86, -180, -167, -10, -23, -52, -105, -235, -240, -40, -98, -54, -114, -234, -114, -38, -240, -113, -2, -35, -24, -33, -123, -253, -127, -23, -202, -78, -80, -158, -149, -227, -35, -176, -127, -65, -2, -0, -153, -220, -86, -89, -92, -149, -233, -200, -206, -185, -135, -136, -173, -240, -38, -98, -218, -35, -34, -42, -20, -35, -243, -138, -41, -76, -188, -147, -150, -252, -216, -230, -228, -66, -121, -213, -150, -169, -237, -234, -107, -154, -167, -174, -183, -227, -92, -68, -184, -216, -112, -241, -179, -144, -175, -120, -169, -128, -113, -218, -173, -132, -40, -64, -175, -32, -183, -41, -120, -130, -0, -15, -199, -132, -126, -225, -186, -93, -254, -69, -255, -190, -57, -100, -224, -24, -99, -172, -239, -243, -233, -30, -18, -235, -86, -121, -119, -96, -63, -108, -128, -145, -103, -246, -204, -203, -122, -118, -133, -106, -120, -175, -212, -191, -62, -11, -193, -233, -250, -247, -233, -182, -0, -241, -28, -219, -215, -24, -243, -152, -49, -166, -184, -135, -92, -174, -208, -206, -197, -3, -250, -119, -221, -192, -126, -217, -120, -241, -119, -121, -234, -172, -146, -241, -156, -44, -203, -30, -242, -212, -63, -133, -44, -203, -30, -53, -198, -88, -251, -251, -218, -19, -133, -44, -203, -230, -24, -99, -78, -49, -198, -172, -105, -228, -190, -79, -200, -178, -44, -228, -136, -108, -145, -49, -102, -101, -99, -204, -22, -198, -152, -101, -140, -140, -181, -151, -100, -89, -118, -71, -64, -219, -132, -30, -225, -10, -128, -51, -140, -76, -178, -101, -141, -49, -135, -213, -180, -251, -137, -145, -1, -125, -179, -49, -230, -175, -158, -122, -155, -81, -40, -200, -218, -204, -24, -51, -75, -255, -250, -194, -28, -197, -228, -21, -83, -152, -216, -201, -181, -142, -49, -198, -32, -75, -232, -99, -140, -49, -75, -26, -99, -246, -203, -178, -172, -24, -74, -202, -78, -218, -251, -61, -188, -174, -214, -191, -161, -89, -100, -172, -64, -242, -153, -81, -199, -20, -114, -3, -200, -178, -236, -19, -89, -142, -111, -7, -182, -153, -145, -101, -217, -58, -89, -150, -45, -147, -101, -217, -74, -89, -150, -189, -61, -203, -178, -59, -3, -251, -151, -208, -35, -158, -18, -0, -89, -150, -205, -53, -198, -124, -220, -24, -179, -208, -24, -179, -43, -21, -105, -143, -140, -49, -187, -25, -99, -230, -26, -99, -62, -146, -101, -217, -66, -15, -223, -25, -250, -119, -57, -79, -157, -15, -85, -95, -199, -152, -188, -98, -10, -19, -59, -249, -236, -196, -217, -215, -200, -228, -59, -47, -203, -50, -95, -54, -88, -75, -119, -165, -167, -174, -237, -234, -107, -192, -175, -222, -196, -21, -114, -9, -139, -57, -70, -68, -94, -201, -178, -236, -28, -99, -204, -123, -141, -49, -79, -24, -99, -118, -49, -198, -76, -193, -73, -123, -100, -100, -201, -185, -171, -49, -230, -65, -99, -204, -142, -89, -150, -149, -125, -101, -218, -46, -105, -125, -95, -199, -152, -188, -98, -10, -147, -127, -232, -223, -29, -16, -141, -243, -119, -140, -8, -150, -189, -139, -132, -1, -91, -166, -54, -171, -175, -27, -77, -46, -56, -92, -196, -20, -114, -227, -2, -136, -157, -197, -41, -148, -28, -143, -33, -217, -129, -254, -136, -39, -100, -151, -214, -23, -209, -58, -200, -40, -18, -196, -117, -111, -224, -31, -192, -125, -136, -237, -197, -52, -228, -136, -239, -135, -56, -14, -113, -37, -237, -59, -185, -168, -211, -209, -249, -203, -50, -233, -148, -246, -136, -184, -10, -197, -152, -188, -98, -158, -116, -44, -67, -158, -196, -209, -42, -210, -6, -142, -4, -149, -182, -54, -83, -44, -205, -146, -78, -206, -162, -228, -152, -139, -136, -199, -185, -227, -5, -58, -232, -209, -231, -252, -101, -224, -6, -125, -39, -147, -145, -99, -189, -83, -181, -222, -39, -48, -125, -199, -138, -173, -130, -140, -34, -169, -207, -111, -119, -218, -77, -69, -20, -159, -55, -146, -91, -38, -86, -230, -112, -164, -163, -139, -58, -29, -157, -191, -162, -128, -136, -121, -212, -35, -243, -138, -38, -76, -180, -126, -123, -231, -197, -78, -7, -54, -45, -212, -55, -202, -20, -75, -120, -210, -201, -109, -43, -120, -68, -19, -114, -49, -64, -216, -241, -226, -234, -192, -33, -136, -127, -195, -92, -202, -225, -245, -55, -64, -162, -232, -252, -202, -161, -187, -29, -17, -162, -147, -157, -178, -83, -40, -113, -55, -118, -104, -90, -7, -25, -5, -94, -64, -46, -124, -255, -11, -108, -83, -168, -95, -11, -137, -16, -84, -155, -48, -148, -14, -46, -234, -68, -112, -254, -234, -12, -34, -230, -81, -143, -204, -43, -154, -48, -81, -154, -103, -146, -219, -3, -128, -56, -108, -220, -197, -160, -197, -93, -112, -166, -88, -6, -87, -95, -11, -201, -133, -204, -69, -212, -251, -40, -68, -21, -114, -129, -60, -90, -135, -144, -215, -251, -181, -95, -205, -219, -129, -99, -128, -163, -129, -91, -157, -231, -119, -55, -226, -33, -87, -122, -239, -228, -246, -242, -199, -49, -210, -97, -201, -90, -28, -126, -161, -162, -173, -69, -171, -32, -163, -74, -99, -45, -16, -175, -37, -66, -204, -126, -58, -186, -168, -143, -58, -136, -152, -71, -61, -22, -47, -34, -10, -19, -165, -57, -70, -105, -14, -71, -246, -138, -255, -209, -151, -22, -37, -83, -172, -115, -29, -43, -184, -102, -2, -149, -186, -16, -34, -11, -185, -192, -254, -29, -71, -203, -16, -242, -192, -79, -245, -250, -255, -194, -49, -224, -65, -246, -237, -103, -107, -93, -109, -48, -76, -100, -245, -48, -3, -88, -190, -80, -158, -33, -1, -54, -7, -220, -130, -29, -26, -139, -86, -65, -70, -201, -45, -63, -33, -32, -136, -232, -132, -1, -17, -243, -168, -199, -226, -69, -92, -193, -180, -2, -146, -66, -189, -215, -220, -121, -200, -18, -215, -46, -103, -47, -5, -158, -94, -65, -27, -85, -200, -5, -246, -175, -117, -8, -121, -114, -147, -224, -29, -61, -117, -111, -214, -186, -255, -5, -244, -97, -54, -37, -201, -57, -144, -85, -209, -204, -138, -182, -22, -173, -130, -140, -34, -43, -22, -40, -49, -227, -29, -22, -144, -109, -198, -65, -136, -192, -155, -129, -8, -247, -255, -34, -65, -74, -26, -101, -75, -50, -192, -243, -145, -101, -213, -165, -200, -87, -109, -46, -98, -111, -125, -35, -98, -6, -250, -57, -28, -39, -138, -10, -62, -209, -242, -168, -199, -226, -69, -68, -193, -52, -44, -32, -49, -16, -173, -91, -241, -5, -84, -36, -153, -32, -162, -144, -11, -236, -91, -107, -231, -20, -242, -61, -255, -102, -158, -186, -205, -180, -110, -150, -175, -109, -129, -246, -118, -68, -219, -62, -32, -28, -17, -13, -252, -109, -21, -109, -67, -4, -64, -105, -144, -81, -157, -112, -0, -223, -173, -235, -103, -9, -239, -5, -250, -111, -243, -10, -154, -45, -108, -39, -75, -234, -95, -69, -110, -101, -57, -11, -9, -117, -126, -13, -249, -202, -229, -30, -42, -2, -210, -184, -140, -150, -71, -150, -116, -118, -223, -185, -0, -89, -94, -93, -142, -104, -87, -109, -148, -88, -232, -224, -49, -53, -218, -32, -162, -96, -26, -22, -16, -51, -95, -27, -101, -168, -50, -172, -58, -227, -68, -200, -145, -231, -196, -27, -176, -68, -36, -79, -206, -225, -59, -210, -45, -210, -218, -40, -58, -199, -48, -210, -149, -214, -78, -220, -210, -12, -63, -206, -243, -168, -18, -0, -165, -65, -70, -157, -231, -252, -209, -186, -126, -118, -184, -126, -169, -0, -64, -20, -125, -246, -68, -234, -199, -56, -66, -16, -153, -207, -214, -235, -241, -14, -10, -91, -164, -34, -163, -21, -201, -195, -33, -61, -12, -124, -134, -194, -210, -1, -89, -98, -110, -143, -68, -62, -45, -93, -138, -38, -244, -3, -96, -23, -2, -61, -240, -232, -81, -200, -21, -132, -72, -41, -159, -170, -129, -171, -245, -214, -49, -104, -50, -78, -0, -77, -36, -169, -198, -127, -237, -164, -174, -233, -75, -230, -240, -1, -249, -96, -157, -236, -180, -135, -138, -175, -115, -224, -4, -44, -13, -50, -74, -67, -103, -174, -150, -215, -175, -18, -0, -214, -255, -165, -204, -147, -48, -67, -20, -211, -0, -159, -173, -234, -136, -117, -150, -185, -135, -228, -168, -145, -80, -1, -70, -162, -42, -225, -103, -157, -0, -88, -93, -39, -44, -136, -128, -186, -65, -133, -129, -221, -26, -76, -193, -73, -217, -85, -194, -227, -56, -165, -253, -13, -162, -140, -189, -17, -89, -6, -223, -164, -191, -109, -236, -127, -239, -185, -122, -224, -4, -252, -168, -210, -12, -4, -25, -37, -63, -254, -251, -112, -85, -63, -43, -120, -119, -21, -0, -151, -106, -213, -167, -42, -218, -127, -86, -105, -46, -40, -35, -120, -137, -211, -145, -237, -189, -68, -9, -9, -10, -29, -39, -139, -16, -101, -211, -92, -10, -49, -7, -28, -186, -74, -1, -160, -52, -171, -146, -123, -125, -46, -64, -244, -18, -55, -35, -231, -230, -171, -5, -244, -229, -173, -192, -31, -40, -183, -4, -92, -22, -73, -178, -234, -141, -242, -27, -56, -1, -171, -130, -140, -90, -219, -139, -218, -4, -174, -29, -174, -95, -37, -0, -108, -44, -132, -82, -215, -108, -224, -141, -101, -2, -204, -18, -28, -166, -4, -215, -182, -185, -137, -16, -208, -33, -149, -23, -49, -76, -28, -19, -162, -193, -25, -180, -223, -210, -191, -139, -86, -90, -155, -0, -0, -32, -0, -73, -68, -65, -84, -222, -140, -196, -129, -2, -224, -215, -136, -206, -105, -167, -254, -122, -92, -142, -192, -9, -88, -21, -100, -212, -218, -94, -84, -166, -18, -239, -120, -253, -42, -1, -96, -149, -195, -85, -39, -86, -175, -84, -154, -1, -119, -116, -75, -96, -247, -254, -223, -111, -115, -19, -33, -160, -67, -42, -47, -122, -48, -113, -108, -34, -144, -234, -94, -18, -178, -207, -178, -3, -225, -34, -156, -56, -243, -139, -35, -156, -231, -241, -108, -242, -0, -40, -3, -105, -189, -3, -5, -192, -44, -228, -200, -106, -83, -106, -50, -3, -247, -129, -128, -119, -91, -25, -100, -20, -120, -175, -214, -45, -4, -158, -23, -251, -250, -74, -83, -37, -0, -108, -164, -160, -170, -24, -150, -219, -41, -205, -64, -64, -154, -34, -147, -221, -155, -222, -64, -8, -232, -152, -202, -139, -30, -76, -28, -105, -32, -144, -2, -6, -137, -53, -104, -249, -175, -237, -99, -5, -143, -78, -138, -51, -15, -253, -174, -74, -94, -155, -89, -55, -128, -215, -55, -156, -126, -174, -82, -65, -103, -177, -62, -185, -130, -236, -6, -10, -203, -240, -144, -123, -97, -164, -25, -175, -139, -25, -136, -113, -85, -109, -134, -223, -46, -168, -122, -183, -4, -4, -25, -69, -140, -150, -172, -53, -227, -165, -64, -168, -179, -89, -237, -245, -29, -154, -42, -1, -112, -145, -86, -149, -42, -248, -144, -99, -123, -128, -139, -203, -8, -236, -50, -194, -171, -201, -44, -121, -65, -193, -241, -210, -137, -148, -202, -43, -22, -104, -40, -144, -106, -6, -137, -53, -55, -189, -3, -40, -117, -171, -45, -60, -186, -214, -138, -179, -2, -173, -59, -248, -158, -164, -194, -62, -32, -128, -151, -107, -107, -0, -97, -2, -96, -83, -253, -125, -158, -254, -254, -92, -129, -174, -78, -9, -248, -2, -68, -121, -103, -147, -97, -62, -162, -255, -30, -103, -100, -98, -149, -248, -113, -240, -6, -239, -165, -117, -144, -81, -36, -196, -184, -85, -92, -94, -173, -109, -151, -40, -208, -108, -4, -236, -27, -114, -125, -15, -77, -149, -0, -176, -153, -150, -188, -222, -185, -140, -60, -5, -248, -82, -217, -5, -172, -34, -225, -35, -37, -245, -151, -56, -255, -174, -45, -235, -204, -120, -1, -13, -5, -82, -217, -75, -34, -15, -142, -249, -32, -37, -138, -176, -2, -143, -40, -138, -51, -135, -246, -51, -74, -106, -99, -244, -253, -40, -228, -126, -60, -124, -150, -67, -180, -230, -247, -146, -159, -41, -135, -8, -128, -23, -235, -239, -23, -35, -10, -188, -199, -113, -194, -164, -215, -12, -220, -237, -17, -205, -255, -21, -192, -11, -61, -245, -43, -146, -39, -30, -237, -197, -89, -169, -112, -47, -93, -131, -140, -190, -9, -57, -102, -181, -120, -20, -49, -198, -185, -66, -199, -90, -217, -115, -240, -142, -173, -2, -77, -213, -115, -92, -158, -252, -35, -240, -115, -156, -179, -126, -125, -175, -246, -136, -244, -94, -74, -86, -167, -6, -152, -164, -68, -7, -4, -220, -104, -163, -37, -106, -27, -32, -103, -216, -118, -223, -95, -154, -117, -6, -217, -159, -129, -76, -168, -129, -248, -252, -17, -251, -51, -240, -146, -144, -189, -223, -66, -228, -203, -235, -205, -250, -83, -194, -227, -91, -250, -183, -181, -226, -76, -233, -86, -68, -4, -207, -253, -200, -82, -245, -106, -228, -75, -245, -172, -22, -247, -247, -11, -189, -228, -62, -206, -96, -13, -17, -0, -238, -243, -56, -82, -203, -142, -114, -202, -170, -6, -174, -61, -254, -219, -164, -226, -58, -171, -42, -77, -111, -238, -172, -12, -162, -85, -144, -81, -229, -181, -18, -226, -130, -124, -62, -178, -181, -156, -175, -255, -30, -68, -44, -56, -7, -188, -52, -125, -207, -210, -67, -83, -183, -146, -122, -33, -185, -11, -243, -108, -68, -240, -216, -241, -128, -94, -191, -60, -2, -51, -249, -126, -216, -191, -71, -8, -236, -12, -121, -134, -84, -128, -15, -86, -240, -120, -151, -210, -60, -89, -54, -208, -200, -205, -85, -171, -150, -203, -7, -41, -205, -169, -101, -52, -49, -80, -124, -73, -136, -82, -101, -46, -34, -164, -182, -109, -200, -163, -179, -226, -76, -233, -236, -209, -212, -167, -244, -247, -219, -245, -247, -145, -13, -239, -109, -7, -109, -247, -111, -196, -239, -192, -42, -70, -155, -10, -128, -53, -144, -21, -192, -83, -102, -173, -53, -99, -197, -46, -153, -253, -95, -37, -243, -212, -22, -1, -96, -177, -141, -31, -232, -60, -239, -82, -171, -76, -224, -13, -74, -227, -11, -78, -99, -105, -86, -65, -236, -254, -255, -139, -40, -85, -103, -34, -102, -202, -223, -163, -238, -227, -232, -92, -0, -106, -150, -197, -117, -3, -148, -60, -67, -170, -215, -57, -67, -105, -46, -86, -154, -3, -43, -104, -182, -81, -26, -111, -230, -89, -100, -159, -102, -67, -96, -15, -76, -36, -165, -137, -178, -146, -112, -7, -60, -226, -170, -57, -29, -249, -250, -15, -36, -113, -168, -184, -134, -69, -12, -197, -217, -154, -218, -135, -187, -112, -252, -220, -17, -201, -63, -143, -64, -67, -46, -196, -16, -231, -126, -229, -181, -161, -150, -89, -115, -239, -70, -2, -64, -203, -173, -178, -233, -124, -253, -253, -162, -178, -123, -33, -223, -95, -123, -93, -117, -145, -252, -5, -151, -43, -205, -183, -67, -238, -103, -60, -130, -124, -245, -253, -241, -10, -26, -155, -197, -168, -116, -78, -197, -232, -200, -191, -157, -65, -89, -234, -74, -90, -55, -64, -25, -185, -10, -24, -8, -195, -68, -158, -108, -114, -58, -53, -74, -43, -100, -95, -10, -254, -0, -31, -246, -203, -53, -133, -138, -227, -35, -34, -172, -36, -156, -1, -191, -3, -185, -208, -241, -133, -247, -170, -186, -23, -139, -78, -138, -51, -165, -177, -246, -221, -123, -22, -202, -173, -253, -124, -80, -54, -89, -114, -47, -183, -61, -156, -50, -171, -124, -107, -35, -0, -150, -210, -241, -3, -240, -30, -36, -194, -141, -247, -94, -16, -197, -153, -93, -162, -254, -27, -217, -171, -254, -16, -248, -37, -162, -217, -182, -10, -201, -83, -41, -9, -230, -177, -56, -128, -60, -148, -252, -85, -248, -79, -25, -50, -242, -172, -65, -223, -235, -179, -35, -27, -146, -31, -7, -222, -141, -56, -64, -12, -228, -109, -67, -60, -143, -234, -6, -168, -93, -5, -252, -203, -83, -103, -39, -100, -173, -194, -10, -248, -130, -210, -14, -4, -192, -68, -60, -221, -160, -230, -248, -139, -56, -43, -9, -139, -251, -116, -130, -44, -212, -191, -193, -137, -49, -28, -30, -173, -21, -103, -90, -191, -33, -242, -149, -190, -181, -120, -63, -206, -96, -89, -72, -141, -247, -23, -185, -121, -235, -31, -11, -229, -22, -141, -5, -128, -214, -89, -55, -222, -41, -228, -57, -0, -203, -238, -101, -19, -100, -251, -121, -51, -34, -12, -22, -32, -219, -194, -73, -136, -99, -207, -152, -241, -200, -236, -11, -136, -223, -131, -13, -44, -115, -56, -206, -156, -67, -244, -60, -86, -183, -242, -8, -53, -102, -209, -49, -58, -243, -60, -242, -37, -9, -58, -208, -110, -38, -207, -229, -126, -47, -206, -241, -76, -5, -31, -119, -21, -176, -165, -83, -190, -14, -34, -217, -103, -81, -227, -43, -174, -244, -171, -35, -75, -248, -249, -56, -171, -18, -68, -57, -52, -71, -7, -250, -122, -1, -124, -58, -173, -36, -156, -231, -49, -21, -73, -189, -101, -87, -12, -247, -16, -232, -107, -237, -155, -52, -52, -84, -156, -105, -189, -141, -80, -227, -77, -37, -134, -228, -5, -4, -143, -208, -116, -104, -214, -215, -65, -119, -63, -133, -85, -152, -211, -207, -86, -2, -64, -235, -255, -162, -245, -7, -215, -141, -149, -97, -129, -6, -49, -15, -128, -79, -59, -247, -88, -233, -121, -25, -3, -58, -166, -108, -112, -22, -87, -137, -103, -221, -121, -31, -167, -16, -102, -172, -207, -206, -44, -1, -188, -31, -89, -122, -77, -33, -151, -204, -51, -144, -227, -134, -115, -244, -197, -122, -163, -172, -58, -124, -236, -42, -192, -205, -40, -107, -7, -196, -79, -27, -244, -231, -183, -218, -102, -63, -167, -236, -83, -90, -118, -118, -85, -91, -135, -190, -211, -74, -194, -25, -12, -175, -210, -223, -75, -147, -71, -112, -13, -50, -70, -242, -77, -26, -154, -43, -206, -182, -68, -4, -240, -13, -148, -11, -171, -140, -92, -136, -15, -28, -95, -233, -251, -189, -72, -249, -248, -148, -144, -22, -93, -4, -192, -198, -228, -74, -82, -239, -189, -244, -1, -58, -132, -39, -115, -120, -188, -72, -199, -250, -52, -253, -55, -3, -120, -65, -191, -61, -55, -6, -217, -46, -253, -28, -57, -29, -153, -173, -215, -189, -30, -217, -34, -4, -127, -249, -233, -24, -89, -56, -26, -200, -87, -1, -11, -144, -101, -235, -242, -200, -249, -234, -28, -224, -217, -13, -248, -216, -60, -238, -215, -59, -101, -246, -38, -223, -27, -200, -163, -211, -74, -162, -100, -242, -110, -66, -46, -181, -107, -61, -193, -202, -38, -13, -205, -20, -103, -255, -12, -185, -111, -100, -255, -13, -112, -174, -167, -206, -250, -200, -123, -67, -144, -59, -253, -108, -45, -0, -148, -230, -71, -14, -221, -176, -4, -192, -113, -180, -12, -79, -166, -237, -215, -66, -162, -20, -77, -3, -94, -13, -188, -14, -153, -136, -119, -16, -176, -98, -29, -11, -160, -99, -100, -225, -216, -157, -177, -171, -128, -35, -200, -151, -85, -71, -213, -183, -28, -224, -99, -61, -174, -94, -138, -216, -140, -131, -196, -44, -8, -86, -14, -209, -97, -37, -81, -54, -224, -201, -181, -179, -79, -226, -248, -179, -55, -228, -17, -172, -56, -139, -1, -242, -227, -183, -91, -116, -114, -20, -255, -89, -88, -215, -92, -95, -88, -116, -239, -189, -20, -104, -86, -162, -194, -0, -166, -15, -208, -33, -60, -153, -182, -185, -10, -89, -245, -190, -216, -41, -123, -41, -114, -218, -226, -203, -196, -52, -38, -65, -135, -200, -194, -177, -59, -98, -87, -1, -179, -244, -193, -206, -175, -155, -40, -37, -124, -172, -185, -227, -129, -228, -246, -234, -63, -110, -200, -163, -245, -74, -162, -106, -192, -3, -103, -105, -221, -37, -84, -236, -51, -107, -120, -4, -43, -206, -186, -130, -22, -232, -163, -31, -37, -125, -235, -20, -215, -142, -14, -225, -201, -22, -55, -48, -86, -34, -11, -147, -175, -2, -0, -126, -221, -146, -199, -106, -200, -190, -200, -42, -35, -161, -197, -190, -140, -150, -43, -137, -154, -201, -187, -38, -249, -146, -235, -235, -109, -120, -104, -253, -152, -80, -156, -57, -253, -44, -221, -2, -244, -116, -221, -56, -113, -237, -18, -226, -129, -14, -62, -252, -14, -143, -85, -156, -65, -245, -234, -14, -125, -57, -17, -57, -149, -88, -4, -92, -214, -146, -71, -171, -149, -68, -192, -228, -125, -155, -214, -207, -167, -100, -201, -21, -192, -99, -84, -20, -103, -158, -126, -12, -93, -0, -16, -41, -174, -29, -221, -87, -16, -209, -34, -235, -34, -167, -103, -181, -122, -135, -26, -30, -157, -231, -95, -39, -208, -193, -135, -191, -192, -167, -114, -240, -7, -242, -216, -202, -225, -243, -177, -150, -60, -90, -173, -36, -66, -250, -143, -232, -57, -64, -246, -214, -3, -131, -52, -144, -199, -208, -21, -103, -158, -62, -140, -134, -0, -232, -28, -215, -142, -142, -43, -136, -174, -237, -61, -252, -126, -5, -124, -38, -148, -190, -132, -71, -163, -249, -71, -228, -208, -224, -157, -124, -248, -11, -188, -106, -7, -127, -0, -15, -187, -55, -158, -142, -199, -64, -169, -1, -159, -206, -43, -137, -150, -215, -13, -17, -0, -67, -87, -156, -121, -250, -208, -73, -0, -208, -34, -65, -38, -29, -227, -218, -209, -113, -5, -209, -181, -125, -31, -104, -58, -255, -136, -189, -133, -34, -162, -15, -127, -36, -1, -96, -253, -238, -143, -237, -216, -151, -206, -43, -137, -4, -63, -104, -153, -32, -147, -142, -113, -237, -232, -184, -130, -232, -218, -190, -15, -52, -153, -127, -99, -81, -128, -21, -59, -216, -73, -0, -32, -71, -101, -119, -42, -143, -114, -183, -198, -48, -94, -81, -86, -18, -9, -35, -65, -135, -4, -153, -116, -140, -107, -71, -247, -21, -68, -140, -21, -72, -40, -46, -41, -187, -70, -91, -208, -82, -128, -141, -139, -35, -19, -68, -67, -127, -168, -49, -102, -125, -99, -204, -133, -89, -150, -117, -61, -143, -181, -38, -180, -167, -100, -89, -54, -163, -35, -175, -4, -35, -3, -204, -24, -115, -130, -49, -102, -101, -99, -204, -117, -198, -152, -173, -179, -44, -27, -225, -186, -154, -101, -217, -131, -198, -24, -111, -10, -117, -99, -204, -52, -99, -204, -106, -198, -152, -42, -129, -188, -162, -67, -91, -132, -205, -204, -124, -99, -69, -123, -123, -252, -235, -91, -6, -119, -109, -191, -192, -24, -19, -154, -26, -172, -52, -79, -97, -7, -216, -128, -170, -222, -116, -112, -89, -150, -129, -56, -136, -189, -220, -24, -243, -110, -99, -204, -207, -70, -16, -16, -33, -61, -81, -21, -28, -233, -23, -180, -2, -64, -246, -194, -215, -171, -212, -178, -251, -154, -39, -232, -152, -175, -128, -136, -43, -137, -132, -28, -116, -76, -144, -73, -199, -184, -118, -116, -95, -65, -116, -143, -172, -27, -1, -180, -84, -226, -209, -53, -52, -120, -200, -4, -237, -34, -0, -154, -2, -73, -188, -121, -47, -185, -2, -233, -76, -2, -114, -17, -214, -240, -92, -134, -60, -60, -210, -5, -145, -186, -106, -121, -23, -49, -23, -217, -147, -253, -3, -248, -24, -53, -6, -42, -228, -129, -39, -75, -51, -225, -146, -251, -51, -84, -126, -105, -104, -17, -66, -189, -235, -251, -167, -99, -130, -76, -58, -198, -181, -163, -99, -100, -220, -174, -237, -99, -128, -14, -74, -60, -186, -10, -176, -174, -3, -96, -172, -130, -158, -86, -18, -158, -235, -88, -216, -184, -114, -87, -58, -215, -3, -9, -128, -82, -26, -45, -150, -220, -142, -224, -94, -252, -57, -232, -151, -32, -87, -174, -13, -228, -209, -43, -208, -54, -14, -161, -222, -245, -253, -211, -61, -65, -102, -167, -184, -118, -116, -95, -65, -116, -143, -172, -59, -72, -255, -107, -2, -143, -1, -233, -126, -138, -209, -77, -128, -117, -29, -0, -99, -21, -244, -176, -146, -40, -185, -206, -192, -243, -67, -38, -237, -174, -228, -19, -242, -59, -21, -237, -109, -30, -123, -240, -231, -160, -183, -2, -226, -22, -106, -98, -231, -211, -34, -132, -122, -215, -247, -79, -199, -4, -153, -202, -163, -117, -92, -59, -186, -175, -32, -186, -71, -214, -29, -108, -115, -51, -112, -116, -32, -109, -215, -83, -140, -110, -2, -172, -235, -0, -152, -232, -168, -122, -126, -192, -207, -180, -174, -74, -193, -100, -128, -15, -41, -221, -149, -158, -186, -127, -104, -221, -39, -98, -246, -219, -225, -223, -233, -253, -211, -49, -65, -166, -195, -167, -85, -92, -59, -186, -175, -32, -186, -71, -214, -237, -118, -223, -93, -79, -33, -186, -9, -176, -174, -3, -192, -211, -153, -115, -129, -29, -156, -178, -231, -33, -174, -154, -222, -32, -22, -227, -29, -85, -207, -15, -137, -173, -0, -48, -167, -134, -135, -171, -160, -220, -202, -41, -223, -4, -49, -90, -122, -136, -158, -50, -50, -119, -125, -255, -116, -76, -144, -25, -3, -116, -140, -140, -219, -181, -125, -199, -190, -119, -181, -131, -232, -38, -192, -186, -14, -128, -2, -221, -1, -228, -246, -203, -23, -35, -89, -135, -23, -34, -231, -238, -223, -174, -106, -235, -240, -24, -106, -68, -150, -174, -168, -122, -126, -72, -104, -105, -0, -223, -241, -85, -145, -214, -222, -247, -31, -157, -50, -251, -242, -170, -2, -150, -116, -58, -197, -233, -250, -254, -233, -152, -32, -51, -22, -232, -18, -25, -55, -66, -251, -14, -253, -238, -124, -10, -65, -23, -1, -214, -117, -0, -120, -104, -119, -33, -247, -59, -7, -177, -189, -175, -205, -244, -170, -109, -71, -37, -34, -75, -23, -84, -61, -63, -36, -200, -37, -4, -68, -47, -66, -226, -251, -63, -132, -8, -204, -231, -34, -49, -225, -166, -233, -75, -172, -90, -2, -119, -157, -192, -93, -219, -119, -74, -144, -57, -209, -65, -164, -83, -8, -218, -10, -176, -88, -2, -0, -216, -131, -60, -28, -213, -125, -136, -115, -203, -44, -100, -9, -91, -27, -189, -135, -150, -17, -89, -136, -247, -5, -12, -66, -69, -251, -162, -18, -112, -119, -68, -186, -47, -162, -66, -186, -23, -120, -89, -147, -231, -159, -144, -103, -254, -169, -140, -236, -210, -245, -253, -69, -184, -255, -78, -9, -50, -39, -58, -232, -225, -20, -34, -4, -125, -88, -2, -46, -105, -140, -153, -103, -140, -217, -205, -136, -165, -221, -124, -36, -212, -213, -89, -198, -152, -223, -0, -87, -102, -89, -54, -165, -162, -253, -95, -141, -49, -153, -49, -102, -171, -44, -203, -38, -25, -99, -12, -176, -181, -49, -230, -116, -173, -43, -219, -131, -217, -64, -28, -3, -97, -149, -3, -225, -211, -190, -190, -86, -255, -94, -103, -140, -153, -25, -200, -231, -56, -96, -166, -49, -102, -89, -99, -204, -6, -70, -172, -219, -102, -24, -99, -246, -200, -178, -204, -171, -160, -241, -224, -23, -198, -152, -47, -25, -99, -62, -102, -140, -121, -208, -24, -179, -200, -24, -211, -40, -248, -73, -7, -84, -221, -235, -242, -198, -152, -50, -1, -115, -134, -49, -230, -14, -35, -247, -124, -28, -176, -125, -150, -101, -179, -122, -232, -223, -226, -138, -63, -25, -99, -94, -103, -140, -249, -160, -41, -90, -233, -25, -81, -226, -105, -157, -49, -198, -252, -37, -250, -213, -187, -126, -65, -2, -248, -31, -170, -77, -91, -103, -243, -173, -225, -31, -189, -255, -33, -60, -61, -180, -69, -60, -66, -96, -96, -70, -242, -48, -221, -85, -240, -234, -67, -186, -222, -127, -140, -231, -71, -135, -4, -153, -19, -29, -140, -242, -41, -132, -33, -82, -122, -162, -138, -182, -171, -144, -159, -21, -15, -229, -28, -222, -67, -51, -12, -1, -224, -110, -1, -206, -215, -178, -61, -170, -218, -58, -244, -59, -50, -50, -9, -171, -197, -181, -78, -153, -55, -19, -81, -215, -251, -143, -245, -252, -104, -153, -32, -179, -41, -144, -60, -123, -219, -118, -228, -241, -44, -228, -136, -246, -118, -196, -86, -227, -17, -36, -188, -155, -55, -55, -132, -182, -9, -70, -139, -254, -140, -218, -41, -132, -33, -82, -122, -34, -224, -53, -136, -214, -255, -110, -228, -107, -48, -93, -95, -254, -231, -145, -68, -15, -0, -159, -247, -180, -27, -85, -45, -118, -9, -125, -45, -207, -42, -90, -224, -37, -200, -158, -248, -65, -90, -72, -237, -174, -215, -247, -208, -12, -229, -249, -209, -34, -65, -102, -83, -144, -107, -205, -91, -9, -2, -196, -215, -222, -42, -222, -166, -33, -147, -237, -86, -231, -57, -120, -141, -182, -24, -41, -160, -203, -254, -61, -25, -58, -198, -60, -252, -71, -229, -20, -194, -16, -33, -61, -17, -240, -85, -242, -164, -33, -211, -148, -151, -43, -193, -30, -208, -191, -255, -240, -180, -29, -51, -3, -184, -9, -207, -58, -90, -36, -42, -12, -192, -192, -190, -206, -195, -99, -92, -111, -1, -74, -218, -188, -4, -249, -154, -109, -90, -79, -29, -14, -196, -165, -251, -23, -228, -99, -43, -88, -16, -32, -2, -234, -30, -109, -247, -45, -70, -230, -86, -220, -1, -249, -104, -65, -131, -172, -79, -5, -254, -151, -52, -121, -70, -99, -2, -116, -76, -79, -68, -126, -68, -177, -200, -243, -80, -159, -142, -8, -7, -139, -123, -60, -237, -199, -220, -0, -14, -225, -89, -71, -11, -60, -83, -7, -212, -2, -60, -145, -112, -10, -180, -227, -126, -11, -224, -105, -243, -7, -29, -19, -231, -19, -89, -8, -40, -255, -53, -17, -239, -57, -107, -136, -84, -43, -8, -128, -47, -42, -173, -55, -115, -18, -240, -93, -173, -255, -83, -203, -62, -141, -63, -1, -96, -204, -83, -3, -176, -85, -122, -34, -242, -208, -216, -165, -81, -127, -129, -19, -44, -111, -79, -221, -152, -27, -192, -33, -60, -67, -104, -129, -175, -105, -221, -69, -33, -215, -141, -125, -125, -135, -102, -52, -4, -192, -187, -17, -223, -4, -128, -107, -67, -219, -53, -5, -242, -85, -223, -159, -124, -149, -89, -42, -8, -128, -11, -149, -198, -107, -181, -136, -232, -49, -0, -166, -182, -236, -203, -248, -20, -0, -198, -24, -67, -203, -244, -68, -72, -104, -109, -128, -247, -87, -208, -188, -85, -105, -230, -122, -234, -198, -220, -0, -142, -53, -1, -145, -21, -208, -93, -90, -255, -65, -95, -251, -62, -175, -239, -208, -12, -93, -0, -56, -109, -47, -5, -158, -104, -218, -174, -197, -117, -150, -5, -62, -129, -56, -67, -129, -223, -110, -222, -245, -210, -172, -194, -194, -150, -125, -24, -55, -2, -96, -192, -14, -32, -203, -178, -59, -141, -49, -109, -76, -111, -173, -146, -235, -222, -10, -154, -39, -245, -239, -99, -45, -248, -143, -91, -100, -89, -54, -27, -241, -243, -255, -157, -49, -230, -71, -192, -153, -197, -104, -57, -17, -48, -211, -200, -57, -125, -85, -228, -87, -107, -137, -57, -148, -40, -72, -136, -99, -16, -70, -108, -7, -182, -52, -198, -252, -97, -24, -215, -213, -107, -90, -248, -140, -146, -236, -88, -253, -175, -233, -231, -89, -124, -222, -24, -51, -16, -80, -213, -17, -10, -27, -103, -89, -118, -91, -27, -198, -192, -134, -198, -152, -111, -24, -99, -182, -55, -198, -172, -110, -196, -78, -228, -76, -99, -204, -1, -89, -150, -61, -220, -174, -187, -17, -64, -190, -244, -218, -165, -130, -230, -195, -74, -243, -119, -79, -221, -152, -251, -130, -133, -240, -28, -43, -32, -210, -41, -78, -228, -62, -205, -64, -180, -245, -83, -144, -227, -182, -222, -130, -81, -34, -91, -128, -47, -57, -227, -112, -10, -176, -15, -176, -172, -135, -214, -58, -222, -148, -154, -221, -246, -212, -71, -139, -141, -90, -182, -127, -41, -185, -158, -110, -6, -146, -241, -218, -42, -65, -239, -35, -32, -75, -118, -111, -32, -79, -89, -253, -199, -10, -154, -191, -41, -205, -94, -158, -186, -174, -2, -32, -186, -29, -195, -56, -19, -0, -157, -79, -113, -198, -35, -16, -37, -224, -247, -200, -109, -76, -110, -70, -204, -175, -75, -173, -92, -201, -93, -111, -191, -48, -228, -190, -182, -22, -0, -136, -89, -185, -77, -113, -127, -2, -42, -76, -129, -149, -17, -69, -43, -104, -114, -217, -81, -1, -114, -220, -99, -207, -102, -191, -138, -35, -121, -145, -125, -217, -119, -156, -23, -52, -224, -210, -26, -65, -0, -68, -255, -2, -142, -51, -1, -208, -233, -20, -103, -188, -1, -57, -6, -252, -57, -249, -23, -112, -18, -226, -118, -93, -25, -44, -69, -219, -238, -167, -109, -174, -199, -19, -125, -169, -47, -116, -20, -0, -111, -215, -182, -247, -2, -79, -43, -212, -45, -79, -158, -154, -238, -85, -49, -59, -124, -24, -13, -98, -160, -3, -31, -32, -63, -49, -152, -129, -156, -32, -92, -77, -254, -117, -190, -7, -120, -97, -73, -219, -174, -2, -32, -250, -23, -112, -60, -9, -0, -99, -186, -157, -226, -140, -55, -144, -127, -108, -174, -4, -222, -233, -123, -231, -21, -109, -87, -64, -182, -8, -32, -17, -162, -54, -44, -212, -63, -7, -81, -36, -238, -80, -198, -163, -101, -159, -187, -8, -0, -43, -192, -189, -246, -36, -228, -89, -169, -126, -208, -189, -167, -57, -83, -104, -24, -195, -28, -9, -252, -113, -4, -249, -41, -130, -141, -106, -250, -29, -186, -231, -155, -175, -18, -0, -209, -191, -128, -93, -5, -0, -34, -248, -162, -41, -153, -144, -124, -134, -223, -167, -58, -55, -94, -171, -83, -156, -241, -6, -196, -123, -238, -205, -29, -218, -111, -70, -126, -50, -3, -242, -5, -157, -66, -110, -4, -4, -145, -227, -80, -56, -124, -159, -131, -28, -143, -90, -171, -65, -155, -190, -252, -45, -21, -109, -255, -163, -109, -119, -43, -169, -223, -93, -235, -207, -139, -221, -225, -232, -73, -12, -74, -174, -213, -121, -15, -79, -228, -47, -96, -4, -1, -112, -59, -208, -72, -219, -203, -96, -88, -232, -39, -245, -30, -190, -133, -44, -119, -103, -0, -207, -106, -211, -159, -190, -129, -68, -93, -62, -7, -103, -27, -230, -43, -107, -200, -115, -91, -34, -71, -112, -118, -120, -175, -12, -124, -5, -89, -69, -76, -67, -140, -181, -30, -211, -201, -246, -157, -216, -2, -211, -25, -79, -63, -212, -191, -247, -1, -151, -147, -7, -3, -5, -143, -153, -188, -182, -181, -202, -77, -239, -252, -32, -143, -22, -52, -96, -100, -215, -181, -195, -195, -18, -0, -177, -124, -17, -162, -125, -1, -35, -8, -128, -127, -211, -32, -76, -54, -254, -176, -208, -147, -28, -1, -6, -112, -112, -155, -190, -12, -3, -120, -150, -169, -190, -178, -64, -94, -219, -34, -198, -60, -208, -99, -28, -254, -97, -194, -121, -135, -115, -113, -98, -99, -32, -219, -83, -27, -247, -97, -30, -158, -120, -10, -228, -186, -14, -111, -206, -5, -224, -101, -90, -31, -239, -88, -115, -200, -2, -96, -92, -107, -177, -129, -53, -144, -200, -174, -155, -57, -101, -167, -227, -152, -147, -34, -169, -179, -190, -136, -39, -203, -43, -213, -97, -161, -63, -224, -12, -158, -41, -140, -193, -116, -102, -228, -71, -188, -215, -160, -10, -96, -95, -89, -0, -31, -119, -226, -207, -70, -236, -253, -215, -239, -181, -243, -67, -130, -243, -14, -189, -193, -93, -200, -79, -201, -14, -241, -212, -217, -188, -138, -155, -149, -180, -125, -177, -214, -183, -50, -94, -50, -192, -15, -8, -199, -59, -91, -93, -164, -250, -250, -227, -90, -139, -141, -248, -194, -91, -220, -141, -164, -117, -190, -88, -255, -29, -73, -174, -116, -2, -216, -201, -211, -222, -27, -22, -26, -241, -37, -176, -86, -150, -118, -25, -232, -93, -38, -142, -22, -128, -205, -145, -47, -212, -116, -244, -235, -229, -43, -171, -225, -225, -78, -252, -39, -16, -93, -199, -154, -253, -247, -126, -120, -112, -222, -255, -219, -74, -234, -109, -236, -200, -171, -61, -117, -118, -107, -91, -183, -2, -8, -13, -92, -51, -192, -224, -19, -12, -186, -53, -162, -147, -178, -88, -254, -186, -138, -155, -11, -66, -73, -31, -198, -173, -22, -27, -17, -0, -255, -208, -254, -22, -149, -73, -211, -181, -236, -106, -100, -63, -60, -224, -111, -142, -39, -44, -52, -178, -234, -177, -225, -192, -143, -5, -246, -213, -255, -15, -100, -215, -85, -250, -205, -41, -28, -17, -57, -117, -75, -81, -227, -75, -142, -104, -199, -191, -174, -207, -221, -26, -241, -60, -140, -8, -177, -239, -80, -208, -150, -107, -155, -149, -200, -205, -110, -63, -92, -86, -86, -113, -77, -119, -226, -79, -69, -4, -225, -74, -85, -109, -198, -43, -156, -241, -80, -246, -21, -127, -189, -214, -15, -152, -76, -35, -250, -2, -168, -215, -1, -60, -16, -187, -195, -65, -91, -0, -36, -40, -100, -240, -191, -10, -62, -139, -133, -22, -27, -145, -230, -243, -16, -79, -184, -218, -88, -254, -228, -214, -105, -175, -119, -202, -108, -172, -247, -91, -144, -179, -222, -29, -244, -183, -47, -44, -244, -11, -17, -69, -214, -63, -138, -66, -0, -88, -26, -217, -142, -204, -161, -228, -156, -24, -81, -62, -222, -172, -252, -23, -234, -4, -190, -18, -39, -136, -7, -254, -132, -26, -43, -147, -135, -50, -255, -112, -89, -89, -201, -53, -237, -196, -191, -3, -209, -239, -120, -133, -87, -23, -32, -31, -149, -243, -200, -181, -237, -23, -163, -43, -88, -116, -75, -217, -128, -215, -154, -228, -202, -234, -43, -90, -244, -197, -98, -227, -146, -250, -170, -220, -133, -54, -102, -96, -153, -3, -147, -61, -5, -184, -172, -105, -191, -234, -58, -60, -20, -29, -64, -87, -32, -57, -7, -64, -190, -164, -3, -86, -96, -200, -23, -208, -126, -101, -75, -61, -22, -11, -109, -26, -217, -65, -56, -237, -94, -128, -172, -100, -78, -1, -206, -70, -4, -153, -87, -234, -59, -109, -70, -132, -133, -70, -142, -169, -230, -32, -66, -228, -101, -90, -246, -170, -138, -1, -178, -52, -112, -134, -214, -63, -37, -4, -16, -45, -252, -153, -90, -126, -22, -37, -123, -113, -224, -120, -165, -185, -141, -66, -4, -102, -196, -200, -235, -8, -74, -162, -58, -35, -38, -170, -179, -25, -185, -5, -24, -40, -243, -180, -179, -58, -143, -219, -145, -21, -104, -84, -1, -64, -158, -72, -3, -100, -251, -228, -70, -37, -250, -50, -170, -88, -107, -192, -239, -40, -135, -215, -128, -30, -39, -160, -189, -133, -55, -232, -13, -249, -87, -252, -113, -79, -221, -33, -90, -119, -120, -73, -219, -95, -104, -125, -101, -240, -216, -54, -29, -30, -47, -2, -96, -57, -96, -178, -246, -249, -32, -79, -253, -247, -181, -110, -18, -129, -201, -53, -218, -220, -63, -226, -249, -103, -83, -124, -109, -79, -254, -213, -190, -145, -234, -243, -123, -27, -157, -230, -77, -192, -211, -28, -30, -95, -118, -104, -42, -195, -66, -35, -66, -224, -207, -74, -115, -54, -242, -37, -254, -171, -254, -46, -157, -252, -218, -214, -158, -62, -148, -250, -114, -212, -220, -183, -253, -2, -185, -74, -192, -129, -178, -66, -155, -101, -17, -123, -125, -171, -31, -153, -138, -184, -244, -118, -222, -2, -0, -27, -147, -199, -39, -252, -63, -84, -177, -140, -108, -171, -62, -70, -174, -84, -11, -245, -11, -121, -185, -182, -121, -156, -246, -167, -66, -22, -239, -40, -169, -255, -148, -214, -95, -229, -169, -179, -249, -29, -239, -163, -218, -18, -208, -171, -95, -104, -5, -101, -56, -46, -4, -128, -49, -198, -0, -155, -34, -75, -180, -69, -192, -118, -78, -249, -246, -90, -54, -29, -216, -164, -1, -191, -198, -247, -79, -158, -6, -236, -78, -29, -108, -25, -112, -131, -150, -253, -178, -162, -221, -83, -97, -161, -129, -159, -234, -255, -207, -199, -49, -109, -37, -32, -44, -52, -242, -197, -183, -66, -192, -218, -199, -87, -78, -126, -109, -103, -151, -182, -91, -85, -209, -213, -240, -176, -249, -15, -126, -86, -85, -230, -105, -183, -52, -34, -44, -236, -22, -228, -9, -196, -22, -162, -181, -18, -144, -60, -8, -173, -215, -48, -134, -60, -54, -69, -168, -0, -184, -17, -17, -80, -91, -118, -232, -147, -197, -175, -74, -234, -207, -211, -250, -31, -150, -212, -91, -189, -220, -111, -200, -125, -1, -86, -4, -78, -213, -242, -235, -9, -48, -133, -110, -210, -225, -237, -232, -43, -8, -97, -79, -0, -62, -168, -15, -99, -42, -178, -175, -93, -139, -124, -217, -87, -26, -171, -160, -132, -23, -52, -23, -0, -107, -35, -58, -140, -253, -157, -178, -143, -35, -91, -148, -117, -42, -218, -89, -5, -223, -221, -136, -176, -122, -12, -88, -215, -169, -15, -78, -78, -137, -40, -243, -236, -201, -193, -163, -192, -138, -1, -253, -182, -118, -24, -165, -9, -76, -3, -120, -44, -139, -40, -57, -247, -170, -42, -171, -104, -111, -147, -169, -218, -213, -207, -44, -125, -150, -235, -183, -232, -203, -53, -202, -99, -159, -146, -250, -157, -155, -8, -128, -24, -112, -4, -192, -2, -36, -252, -155, -187, -42, -249, -63, -173, -155, -75, -137, -169, -48, -162, -31, -179, -39, -65, -51, -16, -161, -100, -149, -230, -143, -83, -19, -113, -106, -204, -67, -7, -192, -62, -136, -1, -141, -61, -18, -244, -162, -134, -143, -205, -85, -127, -142, -254, -131, -22, -123, -35, -109, -55, -44, -59, -136, -21, -200, -21, -103, -224, -40, -123, -104, -16, -22, -154, -145, -219, -0, -43, -4, -254, -130, -19, -162, -173, -164, -157, -141, -90, -52, -15, -89, -138, -46, -89, -69, -223, -39, -116, -66, -188, -19, -81, -66, -66, -11, -67, -32, -242, -16, -97, -222, -184, -126, -136, -94, -163, -118, -44, -197, -132, -243, -110, -109, -170, -239, -7, -24, -180, -4, -172, -212, -57, -33, -31, -152, -35, -17, -191, -154, -185, -250, -247, -215, -140, -119, -91, -9, -125, -233, -127, -28, -152, -233, -57, -30, -66, -20, -84, -15, -213, -189, -52, -100, -15, -125, -157, -211, -246, -42, -234, -151, -192, -163, -106, -7, -161, -125, -248, -142, -115, -141, -89, -72, -44, -192, -38, -201, -45, -93, -69, -224, -25, -200, -222, -240, -116, -253, -93, -41, -4, -180, -173, -213, -23, -128, -8, -163, -175, -50, -202, -103, -241, -192, -155, -105, -24, -74, -77, -219, -217, -108, -197, -175, -40, -169, -223, -216, -222, -104, -5, -143, -198, -97, -195, -107, -250, -100, -241, -44, -100, -165, -243, -31, -100, -91, -58, -29, -9, -83, -22, -111, -255, -62, -222, -64, -158, -65, -183, -12, -159, -80, -186, -79, -84, -189, -52, -135, -159, -155, -92, -116, -239, -0, -250, -78, -118, -16, -93, -1, -60, -3, -249, -106, -205, -5, -14, -70, -150, -228, -51, -144, -37, -222, -36, -234, -211, -99, -219, -163, -62, -244, -239, -210, -78, -185, -141, -201, -119, -38, -213, -66, -96, -9, -125, -110, -246, -204, -25, -29, -248, -63, -165, -143, -100, -20, -61, -130, -124, -5, -89, -22, -195, -114, -51, -123, -131, -37, -245, -173, -194, -134, -215, -244, -201, -98, -192, -158, -98, -194, -131, -220, -12, -178, -12, -193, -2, -0, -241, -182, -122, -2, -209, -218, -46, -68, -246, -211, -165, -251, -239, -10, -62, -48, -188, -45, -192, -114, -58, -201, -127, -212, -178, -253, -243, -145, -125, -224, -83, -147, -223, -169, -91, -10, -9, -216, -50, -147, -18, -75, -50, -15, -253, -206, -228, -138, -73, -16, -5, -221, -184, -177, -204, -35, -95, -1, -238, -89, -82, -255, -182, -50, -1, -64, -79, -97, -195, -157, -103, -217, -42, -34, -80, -239, -64, -236, -217, -191, -133, -72, -187, -39, -145, -165, -231, -36, -36, -81, -65, -165, -253, -57, -34, -81, -15, -71, -52, -222, -51, -144, -51, -224, -155, -144, -4, -151, -181, -222, -107, -228, -202, -141, -78, -2, -0, -88, -18, -49, -246, -0, -49, -30, -178, -231, -167, -23, -208, -80, -67, -170, -237, -198, -211, -41, -200, -243, -139, -147, -223, -169, -91, -146, -22, -74, -34, -36, -144, -171, -221, -79, -7, -217, -79, -140, -5, -144, -159, -164, -120, -243, -232, -145, -155, -150, -251, -4, -64, -47, -97, -195, -199, -180, -0, -0, -62, -68, -190, -108, -90, -128, -236, -123, -220, -165, -224, -245, -128, -55, -224, -164, -78, -124, -155, -16, -100, -14, -98, -185, -118, -143, -83, -246, -48, -53, -199, -111, -140, -76, -37, -238, -67, -168, -0, -248, -166, -210, -79, -66, -52, -208, -203, -146, -107, -184, -191, -214, -240, -153, -192, -56, -18, -0, -125, -193, -62, -115, -224, -161, -209, -238, -75, -40, -16, -97, -104, -141, -171, -246, -41, -212, -189, -7, -81, -118, -226, -27, -75, -244, -20, -54, -220, -25, -203, -209, -5, -64, -103, -222, -58, -137, -231, -81, -56, -127, -5, -94, -65, -254, -117, -246, -102, -168, -37, -143, -199, -254, -49, -70, -122, -177, -61, -31, -57, -170, -128, -18, -73, -236, -208, -186, -46, -175, -173, -4, -0, -146, -78, -124, -1, -34, -76, -94, -236, -148, -111, -174, -101, -243, -9, -76, -209, -173, -237, -146, -0, -48, -198, -0, -175, -213, -103, -49, -16, -206, -61, -160, -109, -48, -2, -120, -185, -122, -157, -218, -96, -29, -228, -71, -171, -144, -251, -222, -219, -165, -189, -253, -138, -251, -4, -64, -47, -97, -195, -157, -118, -99, -82, -0, -44, -65, -121, -184, -174, -189, -148, -249, -93, -21, -109, -189, -91, -4, -224, -29, -218, -182, -210, -79, -153, -145, -222, -114, -62, -84, -10, -0, -100, -223, -118, -135, -210, -250, -108, -214, -247, -215, -186, -219, -9, -56, -23, -215, -54, -227, -206, -14, -162, -15, -56, -239, -255, -198, -22, -109, -67, -114, -233, -93, -66, -141, -160, -5, -94, -132, -108, -45, -167, -233, -191, -25, -20, -76, -150, -75, -218, -189, -13, -249, -162, -91, -109, -251, -127, -128, -247, -2, -235, -218, -129, -229, -105, -99, -87, -14, -215, -117, -233, -179, -135, -111, -183, -73, -58, -74, -188, -173, -70, -20, -96, -94, -139, -182, -219, -106, -219, -1, -251, -230, -2, -221, -239, -171, -231, -127, -173, -0, -56, -73, -233, -46, -194, -179, -215, -71, -132, -148, -85, -106, -157, -232, -169, -247, -157, -2, -148, -253, -235, -124, -10, -128, -8, -151, -179, -144, -175, -205, -28, -68, -195, -124, -8, -125, -38, -128, -44, -239, -203, -230, -200, -113, -215, -43, -139, -207, -14, -177, -162, -180, -95, -196, -74, -3, -164, -30, -251, -183, -22, -114, -44, -57, -13, -89, -229, -189, -14, -17, -0, -119, -0, -107, -181, -228, -185, -161, -29, -88, -158, -186, -81, -9, -27, -222, -5, -125, -11, -0, -187, -231, -169, -74, -250, -81, -214, -214, -58, -41, -84, -42, -144, -24, -233, -79, -223, -88, -0, -116, -5, -67, -180, -3, -32, -95, -141, -128, -124, -149, -110, -38, -215, -129, -220, -71, -3, -115, -229, -24, -32, -119, -50, -2, -153, -100, -215, -33, -138, -96, -55, -205, -247, -31, -168, -8, -181, -221, -115, -255, -174, -66, -86, -136, -238, -182, -238, -165, -72, -76, -191, -1, -187, -249, -64, -158, -27, -217, -27, -243, -212, -141, -74, -216, -240, -46, -112, -222, -211, -6, -125, -48, -183, -17, -71, -143, -9, -164, -95, -6, -217, -255, -255, -4, -81, -4, -94, -67, -137, -39, -89, -161, -221, -215, -201, -21, -135, -69, -244, -42, -0, -134, -5, -228, -43, -187, -72, -39, -252, -7, -200, -205, -65, -159, -129, -216, -254, -163, -19, -208, -231, -209, -248, -74, -224, -159, -200, -215, -111, -22, -98, -26, -252, -85, -224, -185, -14, -205, -218, -192, -111, -27, -246, -105, -69, -36, -54, -222, -121, -200, -22, -105, -58, -162, -15, -186, -15, -49, -40, -122, -87, -135, -251, -13, -70, -219, -107, -180, -236, -87, -149, -0, -24, -149, -176, -225, -93, -224, -60, -198, -117, -128, -93, -16, -33, -22, -20, -112, -180, -142, -241, -115, -144, -37, -234, -2, -2, -150, -23, -133, -119, -58, -9, -177, -121, -14, -126, -136, -136, -100, -63, -88, -7, -222, -223, -201, -221, -124, -23, -23, -1, -96, -183, -42, -62, -143, -197, -101, -201, -245, -24, -239, -241, -212, -91, -69, -230, -181, -140, -52, -74, -1, -81, -182, -94, -133, -76, -220, -57, -195, -185, -155, -122, -16, -73, -7, -208, -67, -191, -170, -4, -192, -168, -132, -13, -239, -2, -103, -28, -88, -147, -241, -224, -128, -163, -85, -76, -151, -6, -46, -211, -198, -65, -241, -198, -145, -175, -215, -36, -196, -1, -103, -33, -114, -58, -112, -8, -29, -44, -201, -22, -51, -1, -112, -183, -222, -207, -75, -74, -234, -109, -16, -144, -147, -61, -117, -39, -1, -207, -116, -126, -111, -4, -124, -67, -5, -194, -60, -196, -208, -231, -92, -26, -156, -116, -140, -7, -16, -217, -36, -87, -121, -150, -10, -0, -173, -111, -21, -54, -92, -203, -175, -211, -255, -55, -10, -253, -221, -165, -207, -78, -159, -230, -225, -56, -189, -17, -16, -112, -180, -234, -130, -191, -210, -134, -151, -208, -98, -255, -135, -44, -107, -191, -174, -23, -190, -159, -246, -57, -209, -22, -39, -1, -96, -207, -159, -159, -81, -82, -111, -143, -220, -90, -37, -143, -92, -220, -64, -15, -38, -185, -202, -183, -82, -0, -40, -77, -227, -176, -225, -202, -114, -22, -178, -53, -131, -24, -95, -226, -128, -62, -59, -188, -27, -7, -28, -45, -187, -216, -193, -218, -224, -150, -178, -193, -218, -160, -227, -214, -56, -231, -172, -150, -237, -163, -11, -0, -26, -160, -130, -199, -70, -136, -95, -249, -125, -200, -210, -252, -126, -196, -87, -187, -84, -202, -234, -64, -2, -120, -126, -73, -253, -139, -180, -62, -118, -246, -224, -113, -7, -122, -50, -201, -237, -19, -206, -176, -105, -28, -250, -59, -128, -119, -136, -0, -104, -28, -112, -212, -71, -252, -37, -37, -190, -31, -71, -193, -212, -22, -228, -199, -136, -173, -34, -150, -106, -219, -216, -2, -96, -114, -205, -191, -59, -170, -250, -140, -28, -157, -89, -203, -201, -153, -136, -38, -223, -250, -102, -79, -163, -124, -137, -111, -51, -188, -120, -29, -148, -200, -5, -111, -212, -88, -248, -248, -49, -27, -89, -214, -158, -137, -196, -81, -240, -154, -74, -3, -171, -43, -237, -28, -58, -126, -12, -26, -246, -185, -23, -147, -220, -62, -225, -60, -219, -56, -95, -226, -145, -109, -67, -4, -128, -215, -220, -155, -138, -128, -163, -69, -66, -43, -41, -30, -2, -54, -109, -218, -201, -18, -158, -219, -40, -207, -39, -91, -182, -135, -33, -111, -1, -16, -255, -7, -128, -95, -148, -212, -159, -171, -245, -167, -161, -70, -80, -136, -242, -200, -122, -222, -157, -91, -210, -110, -31, -173, -191, -31, -71, -72, -32, -118, -10, -159, -33, -15, -85, -85, -105, -55, -209, -226, -126, -44, -174, -37, -87, -188, -93, -67, -110, -231, -15, -162, -120, -245, -230, -216, -67, -124, -206, -1, -190, -26, -179, -95, -53, -125, -238, -197, -36, -183, -79, -56, -207, -178, -251, -151, -120, -176, -109, -136, -0, -40, -139, -191, -88, -26, -112, -212, -37, -218, -133, -220, -123, -46, -90, -66, -76, -242, -0, -8, -173, -242, -150, -105, -219, -161, -9, -0, -196, -51, -239, -17, -125, -22, -94, -215, -77, -114, -31, -253, -77, -10, -229, -207, -215, -242, -178, -149, -195, -146, -72, -188, -62, -144, -109, -195, -69, -200, -23, -248, -78, -189, -158, -77, -144, -50, -37, -242, -61, -89, -108, -81, -40, -95, -18, -216, -155, -252, -248, -213, -123, -228, -135, -156, -206, -128, -40, -49, -135, -18, -44, -132, -158, -76, -114, -251, -132, -211, -167, -198, -161, -191, -29, -154, -98, -36, -227, -75, -16, -235, -197, -245, -44, -243, -138, -235, -54, -10, -56, -234, -198, -155, -219, -193, -24, -115, -146, -49, -102, -166, -49, -102, -199, -44, -203, -130, -82, -104, -107, -219, -115, -145, -44, -48, -43, -23, -202, -215, -0, -14, -52, -198, -88, -103, -12, -175, -47, -193, -24, -196, -158, -198, -152, -213, -141, -49, -103, -100, -89, -118, -123, -9, -205, -52, -253, -187, -160, -80, -190, -72, -255, -122, -95, -112, -150, -101, -11, -141, -49, -59, -25, -99, -190, -96, -140, -153, -108, -140, -121, -153, -49, -102, -107, -99, -204, -29, -198, -152, -237, -140, -49, -147, -148, -244, -250, -86, -61, -111, -136, -44, -203, -22, -102, -89, -246, -75, -99, -204, -63, -181, -104, -235, -18, -186, -107, -140, -49, -151, -25, -99, -214, -53, -198, -120, -131, -90, -246, -0, -59, -158, -254, -107, -140, -185, -180, -226, -95, -235, -80, -216, -200, -138, -237, -54, -59, -118, -129, -85, -145, -237, -223, -192, -41, -76, -67, -148, -29, -197, -206, -210, -191, -222, -0, -177, -72, -68, -160, -191, -25, -99, -94, -175, -180, -55, -27, -99, -54, -50, -198, -156, -106, -140, -217, -55, -224, -186, -235, -151, -148, -91, -157, -147, -127, -60, -147, -239, -95, -167, -82, -189, -55, -30, -152, -196, -142, -244, -89, -132, -40, -109, -254, -135, -124, -209, -236, -87, -101, -1, -78, -116, -219, -166, -80, -30, -67, -89, -1, -32, -190, -240, -246, -248, -231, -213, -21, -116, -39, -42, -205, -247, -10, -229, -54, -241, -99, -144, -209, -148, -135, -175, -221, -215, -182, -142, -205, -87, -194, -215, -194, -187, -178, -67, -18, -143, -0, -28, -86, -193, -99, -87, -165, -137, -151, -129, -182, -2, -12, -193, -36, -23, -209, -107, -0, -188, -86, -127, -219, -232, -187, -179, -91, -242, -179, -104, -19, -250, -187, -117, -36, -99, -231, -186, -173, -2, -142, -54, -209, -138, -159, -230, -105, -187, -35, -162, -13, -159, -132, -156, -151, -206, -71, -20, -97, -147, -17, -43, -194, -78, -193, -10, -245, -186, -195, -18, -0, -187, -233, -245, -42, -147, -122, -34, -203, -49, -235, -37, -249, -83, -96, -3, -242, -173, -206, -245, -180, -8, -158, -161, -47, -218, -186, -46, -87, -230, -17, -104, -193, -219, -98, -64, -0, -32, -250, -7, -27, -64, -99, -192, -0, -201, -161, -91, -218, -185, -103, -175, -227, -88, -76, -48, -4, -147, -92, -6, -87, -0, -171, -32, -43, -128, -83, -90, -242, -179, -104, -19, -250, -187, -117, -36, -99, -231, -186, -173, -2, -142, -38, -152, -167, -30, -150, -141, -72, -91, -107, -254, -138, -72, -236, -171, -25, -137, -95, -2, -171, -180, -188, -190, -245, -186, -59, -191, -77, -251, -26, -222, -3, -2, -0, -49, -217, -222, -20, -56, -70, -235, -254, -76, -137, -18, -208, -105, -99, -227, -22, -30, -25, -187, -143, -158, -107, -141, -103, -147, -220, -198, -95, -98, -58, -68, -50, -214, -226, -7, -201, -117, -72, -141, -3, -142, -78, -120, -32, -209, -111, -64, -108, -31, -106, -163, -7, -233, -132, -189, -23, -145, -186, -214, -113, -230, -6, -26, -154, -136, -34, -193, -59, -191, -170, -124, -102, -81, -178, -124, -236, -2, -170, -113, -17, -162, -195, -169, -156, -252, -202, -231, -89, -200, -57, -246, -116, -122, -142, -19, -200, -248, -54, -201, -109, -252, -37, -166, -67, -36, -99, -45, -254, -147, -254, -63, -5, -28, -109, -3, -114, -119, -225, -79, -214, -208, -45, -1, -252, -86, -105, -47, -70, -52, -255, -203, -34, -73, -61, -158, -64, -116, -31, -7, -86, -180, -255, -53, -146, -190, -235, -207, -72, -8, -116, -27, -12, -229, -1, -122, -74, -126, -234, -12, -76, -247, -24, -240, -74, -36, -142, -224, -2, -189, -247, -215, -4, -242, -58, -89, -121, -125, -174, -143, -190, -22, -174, -213, -202, -36, -55, -128, -175, -133, -111, -34, -214, -90, -9, -6, -240, -109, -28, -250, -155, -8, -145, -140, -19, -90, -2, -241, -51, -7, -9, -95, -86, -153, -58, -140, -220, -162, -235, -66, -10, -81, -118, -145, -175, -210, -157, -90, -255, -145, -146, -246, -199, -32, -147, -126, -1, -114, -212, -117, -33, -98, -244, -210, -219, -23, -213, -25, -124, -190, -99, -192, -143, -146, -7, -80, -173, -253, -154, -146, -43, -202, -110, -33, -96, -213, -208, -21, -180, -48, -201, -13, -224, -217, -183, -0, -104, -28, -250, -155, -142, -145, -140, -163, -0, -241, -40, -67, -59, -28, -108, -245, -165, -15, -237, -24, -68, -241, -247, -184, -182, -191, -9, -113, -96, -241, -90, -197, -105, -187, -189, -169, -135, -215, -168, -166, -192, -103, -101, -100, -207, -120, -33, -50, -169, -230, -107, -31, -130, -246, -170, -228, -201, -52, -106, -181, -239, -228, -169, -175, -203, -94, -148, -205, -225, -55, -102, -236, -249, -157, -103, -89, -119, -10, -16, -116, -164, -134, -172, -36, -96, -12, -45, -191, -155, -192, -121, -30, -125, -9, -128, -198, -161, -191, -233, -16, -201, -56, -26, -144, -244, -67, -54, -209, -96, -80, -54, -29, -196, -44, -214, -218, -184, -207, -69, -20, -105, -215, -144, -47, -211, -230, -1, -59, -149, -180, -253, -134, -210, -84, -29, -63, -86, -30, -169, -33, -150, -134, -110, -84, -225, -7, -181, -221, -20, -224, -204, -128, -254, -111, -138, -44, -219, -103, -19, -32, -244, -200, -29, -122, -188, -209, -142, -129, -103, -107, -253, -88, -114, -203, -173, -19, -0, -31, -209, -250, -89, -190, -122, -15, -253, -158, -74, -223, -202, -191, -99, -180, -49, -4, -1, -208, -88, -219, -78, -135, -72, -198, -81, -1, -108, -73, -30, -87, -255, -229, -1, -244, -103, -105, -191, -46, -99, -100, -64, -209, -229, -200, -143, -46, -38, -151, -180, -181, -254, -203, -94, -205, -103, -192, -181, -55, -35, -183, -202, -251, -13, -133, -227, -41, -2, -180, -199, -228, -49, -7, -74, -147, -119, -22, -232, -173, -75, -175, -247, -235, -135, -88, -84, -2, -220, -20, -118, -23, -253, -35, -64, -0, -216, -19, -136, -202, -216, -141, -14, -253, -211, -17, -47, -189, -133, -244, -17, -133, -166, -103, -140, -81, -1, -208, -58, -146, -113, -116, -168, -16, -120, -0, -184, -33, -128, -214, -106, -47, -7, -98, -229, -145, -155, -144, -122, -191, -134, -192, -41, -90, -223, -202, -186, -140, -252, -88, -165, -84, -233, -86, -211, -126, -29, -125, -176, -139, -8, -244, -208, -34, -63, -175, -189, -157, -130, -177, -16, -98, -19, -97, -87, -80, -251, -151, -241, -24, -54, -2, -4, -128, -117, -253, -190, -188, -1, -79, -123, -228, -116, -104, -188, -158, -14, -7, -99, -81, -0, -104, -251, -170, -72, -198, -95, -71, -21, -198, -109, -120, -247, -134, -154, -135, -121, -144, -214, -253, -179, -164, -237, -249, -90, -95, -187, -210, -240, -180, -125, -177, -182, -157, -66, -77, -242, -203, -10, -30, -118, -50, -255, -185, -65, -155, -21, -201, -189, -250, -64, -190, -132, -55, -50, -50, -161, -233, -25, -4, -158, -93, -3, -107, -146, -167, -231, -190, -34, -176, -77, -211, -16, -217, -22, -69, -37, -96, -6, -236, -65, -174, -129, -174, -205, -226, -235, -180, -125, -46, -185, -239, -200, -114, -161, -237, -106, -250, -215, -118, -226, -108, -139, -124, -76, -110, -211, -73, -50, -3, -49, -170, -58, -20, -143, -14, -170, -102, -204, -142, -154, -0, -80, -30, -222, -72, -198, -90, -119, -93, -155, -228, -11, -222, -105, -0, -0, -32, -0, -73, -68, -65, -84, -126, -245, -10, -223, -77, -35, -103, -184, -54, -55, -252, -109, -192, -179, -75, -218, -94, -175, -52, -222, -250, -154, -235, -30, -168, -109, -127, -210, -178, -223, -171, -146, -235, -41, -26, -69, -250, -69, -172, -226, -246, -65, -142, -207, -30, -211, -9, -244, -40, -18, -179, -239, -169, -88, -127, -129, -188, -142, -210, -62, -60, -0, -60, -39, -128, -190, -113, -136, -108, -231, -29, -185, -199, -128, -151, -147, -103, -16, -6, -57, -222, -107, -154, -61, -233, -47, -218, -246, -227, -77, -218, -85, -244, -175, -205, -210, -217, -21, -134, -183, -35, -177, -18, -239, -33, -55, -157, -29, -88, -29, -86, -93, -143, -252, -195, -50, -42, -2, -96, -84, -65, -139, -240, -75, -197, -155, -70, -190, -12, -54, -106, -203, -239, -168, -176, -140, -115, -6, -224, -13, -228, -122, -135, -25, -136, -34, -241, -48, -96, -227, -138, -182, -214, -183, -250, -3, -136, -34, -240, -92, -228, -4, -194, -102, -214, -221, -151, -81, -138, -100, -27, -10, -36, -86, -194, -66, -237, -119, -173, -7, -38, -45, -67, -100, -227, -199, -34, -114, -161, -181, -107, -203, -254, -91, -151, -220, -255, -182, -105, -239, -233, -95, -27, -1, -96, -141, -176, -182, -43, -148, -175, -130, -152, -118, -15, -40, -107, -201, -133, -195, -139, -60, -117, -54, -151, -197, -216, -250, -210, -246, -13, -90, -134, -95, -42, -190, -60, -242, -188, -238, -181, -54, -220, -192, -57, -74, -251, -24, -18, -84, -99, -50, -249, -30, -26, -68, -8, -121, -191, -46, -228, -86, -98, -135, -32, -95, -224, -123, -144, -47, -210, -69, -228, -10, -147, -191, -50, -138, -185, -238, -235, -128, -108, -29, -166, -2, -91, -6, -210, -71, -15, -145, -61, -22, -208, -81, -0, -216, -49, -251, -170, -6, -109, -236, -24, -123, -183, -167, -238, -232, -9, -39, -0, -232, -16, -126, -201, -35, -0, -172, -86, -62, -40, -35, -47, -158, -76, -61, -192, -243, -200, -45, -206, -22, -1, -219, -122, -104, -158, -112, -234, -191, -228, -78, -116, -196, -108, -210, -30, -13, -214, -166, -7, -79, -24, -93, -116, -20, -0, -214, -131, -114, -26, -18, -200, -165, -54, -89, -8, -185, -221, -199, -21, -56, -33, -235, -17, -109, -187, -213, -196, -79, -40, -1, -208, -58, -252, -146, -71, -0, -156, -161, -191, -255, -71, -71, -175, -54, -135, -215, -217, -158, -58, -171, -184, -250, -105, -73, -91, -107, -100, -84, -233, -217, -151, -48, -250, -112, -198, -208, -250, -72, -120, -178, -43, -244, -67, -242, -36, -178, -69, -169, -212, -209, -0, -7, -56, -60, -230, -35, -137, -76, -94, -91, -65, -255, -26, -103, -252, -204, -64, -156, -186, -172, -201, -241, -183, -24, -35, -218, -118, -2, -17, -227, -66, -173, -195, -47, -57, -253, -176, -2, -96, -53, -196, -198, -29, -100, -41, -254, -77, -90, -46, -195, -145, -99, -53, -240, -251, -79, -91, -205, -185, -55, -12, -54, -98, -224, -3, -53, -161, -200, -144, -213, -198, -137, -200, -82, -124, -62, -178, -31, -191, -4, -201, -224, -227, -205, -136, -172, -237, -90, -5, -147, -64, -146, -119, -124, -13, -81, -194, -77, -211, -107, -78, -69, -132, -221, -86, -177, -219, -141, -7, -56, -99, -200, -166, -115, -191, -27, -217, -130, -218, -119, -60, -159, -146, -248, -12, -192, -203, -144, -45, -228, -99, -200, -135, -234, -50, -135, -223, -241, -192, -178, -37, -237, -222, -128, -156, -66, -77, -67, -38, -252, -53, -168, -249, -54, -37, -218, -246, -226, -88, -47, -212, -133, -132, -236, -170, -132, -167, -93, -85, -46, -5, -107, -143, -210, -42, -212, -94, -241, -66, -173, -195, -47, -249, -30, -10, -162, -37, -255, -6, -121, -128, -131, -179, -129, -167, -181, -232, -151, -245, -128, -26, -8, -208, -128, -40, -42, -161, -160, -252, -113, -234, -159, -161, -245, -165, -89, -109, -17, -187, -118, -235, -140, -51, -23, -209, -67, -184, -105, -209, -75, -99, -236, -211, -50, -152, -4, -185, -185, -245, -124, -228, -20, -228, -42, -242, -237, -204, -2, -224, -245, -49, -219, -85, -244, -227, -45, -218, -54, -232, -232, -209, -211, -126, -64, -168, -35, -19, -174, -18, -37, -188, -44, -230, -3, -187, -56, -229, -171, -59, -247, -93, -182, -58, -181, -31, -155, -55, -59, -101, -219, -144, -7, -118, -245, -174, -16, -219, -192, -233, -103, -83, -1, -112, -82, -200, -191, -6, -253, -88, -146, -252, -4, -173, -117, -176, -29, -151, -97, -235, -140, -168, -53, -15, -101, -11, -114, -79, -168, -160, -228, -34, -133, -246, -239, -212, -182, -183, -122, -234, -172, -17, -145, -247, -1, -144, -11, -143, -187, -43, -248, -219, -243, -252, -75, -113, -204, -128, -17, -171, -172, -255, -171, -233, -91, -171, -96, -18, -136, -78, -229, -211, -56, -206, -63, -136, -213, -164, -221, -151, -150, -5, -20, -109, -213, -174, -162, -31, -246, -20, -197, -187, -234, -83, -154, -181, -17, -99, -171, -199, -245, -126, -183, -208, -242, -221, -128, -251, -60, -244, -239, -69, -242, -44, -22, -255, -253, -81, -175, -229, -181, -52, -116, -198, -208, -81, -158, -58, -235, -7, -255, -96, -73, -91, -43, -192, -87, -45, -148, -111, -169, -229, -211, -124, -237, -218, -160, -102, -172, -183, -182, -31, -104, -209, -15, -107, -185, -121, -55, -45, -62, -172, -62, -134, -173, -195, -47, -85, -61, -20, -173, -183, -166, -177, -222, -244, -226, -53, -188, -173, -153, -241, -64, -116, -94, -29, -108, -32, -202, -203, -1, -15, -62, -224, -251, -90, -95, -154, -43, -143, -124, -133, -210, -58, -99, -75, -44, -144, -123, -36, -54, -90, -210, -181, -105, -135, -100, -199, -93, -132, -28, -161, -121, -151, -200, -74, -119, -28, -178, -23, -159, -172, -244, -139, -244, -255, -179, -8, -207, -21, -185, -20, -249, -87, -220, -235, -106, -237, -140, -161, -1, -111, -57, -242, -92, -9, -197, -248, -139, -182, -222, -58, -103, -125, -177, -80, -190, -154, -150, -63, -28, -210, -207, -16, -140, -5, -1, -128, -196, -143, -176, -10, -238, -82, -225, -221, -148, -105, -235, -240, -75, -1, -2, -96, -11, -173, -247, -45, -227, -255, -12, -236, -4, -172, -84, -40, -127, -22, -185, -121, -234, -116, -96, -125, -79, -219, -165, -200, -61, -168, -46, -70, -181, -191, -136, -117, -219, -222, -200, -228, -94, -68, -197, -241, -144, -211, -247, -129, -243, -224, -97, -131, -138, -156, -245, -177, -219, -1, -63, -214, -38, -3, -57, -10, -11, -116, -83, -129, -15, -232, -255, -95, -140, -172, -2, -108, -154, -171, -160, -212, -220, -136, -82, -13, -224, -156, -10, -26, -139, -1, -165, -113, -221, -196, -66, -44, -231, -108, -252, -201, -19, -201, -117, -81, -54, -191, -197, -9, -33, -253, -12, -188, -151, -210, -177, -94, -215, -207, -136, -125, -248, -182, -94, -230, -26, -98, -185, -99, -211, -33, -252, -146, -251, -80, -244, -101, -63, -203, -169, -91, -154, -220, -34, -112, -96, -175, -233, -180, -93, -132, -68, -215, -185, -158, -145, -250, -136, -71, -41, -217, -227, -107, -251, -231, -147, -239, -217, -231, -33, -123, -120, -55, -88, -68, -229, -254, -200, -161, -107, -44, -0, -104, -153, -179, -14, -17, -92, -31, -68, -142, -57, -39, -33, -210, -124, -58, -185, -86, -186, -108, -160, -183, -106, -231, -225, -179, -28, -121, -32, -144, -245, -234, -250, -26, -194, -179, -162, -253, -75, -245, -189, -60, -65, -197, -177, -176, -59, -134, -60, -117, -33, -105, -188, -94, -67, -190, -202, -88, -132, -40, -74, -23, -33, -218, -253, -213, -187, -220, -67, -204, -126, -70, -184, -254, -51, -201, -21, -163, -241, -130, -165, -210, -33, -252, -146, -251, -80, -156, -23, -112, -51, -98, -16, -100, -39, -243, -130, -146, -182, -111, -67, -162, -235, -220, -138, -44, -43, -109, -176, -135, -203, -144, -211, -131, -218, -224, -154, -136, -178, -239, -80, -229, -49, -87, -219, -159, -77, -64, -242, -72, -167, -239, -141, -4, -0, -237, -141, -166, -214, -36, -247, -165, -183, -207, -197, -70, -82, -182, -171, -25, -159, -18, -169, -85, -187, -146, -62, -216, -253, -227, -233, -77, -238, -185, -41, -144, -152, -131, -54, -198, -226, -110, -53, -180, -79, -141, -33, -79, -93, -240, -196, -2, -94, -133, -172, -28, -109, -148, -235, -203, -112, -18, -170, -118, -69, -172, -126, -118, -184, -190, -141, -223, -248, -215, -62, -152, -119, -201, -136, -138, -62, -128, -189, -244, -161, -79, -67, -36, -255, -20, -100, -153, -223, -200, -214, -190, -111, -144, -107, -240, -189, -168, -105, -219, -197, -104, -234, -55, -90, -55, -21, -49, -58, -121, -186, -83, -87, -165, -69, -110, -213, -174, -164, -255, -255, -85, -242, -202, -47, -8, -29, -143, -29, -17, -229, -31, -4, -164, -238, -114, -199, -144, -167, -174, -241, -196, -66, -78, -15, -78, -213, -102, -165, -91, -143, -166, -168, -233, -103, -175, -17, -123, -128, -23, -34, -130, -127, -1, -125, -69, -101, -166, -135, -240, -75, -99, -17, -136, -143, -194, -105, -206, -11, -61, -71, -127, -159, -134, -39, -244, -121, -161, -109, -23, -163, -41, -187, -106, -24, -8, -23, -70, -158, -67, -209, -39, -0, -90, -181, -243, -208, -110, -173, -164, -33, -110, -222, -173, -143, -29, -17, -197, -164, -13, -150, -26, -178, -138, -171, -154, -88, -173, -190, -172, -136, -16, -128, -192, -32, -39, -129, -60, -45, -124, -62, -4, -189, -70, -236, -33, -63, -181, -57, -186, -15, -254, -19, -18, -85, -47, -180, -162, -77, -23, -163, -41, -123, -234, -176, -173, -167, -206, -38, -21, -241, -9, -128, -86, -237, -60, -180, -246, -171, -248, -169, -0, -218, -182, -199, -149, -79, -71, -182, -128, -224, -177, -181, -47, -105, -211, -74, -0, -32, -142, -103, -251, -81, -80, -18, -35, -129, -91, -191, -172, -205, -188, -193, -104, -218, -192, -233, -167, -207, -135, -224, -136, -190, -4, -0, -121, -66, -145, -25, -44, -38, -31, -225, -49, -129, -150, -2, -160, -139, -209, -148, -221, -175, -31, -135, -42, -216, -116, -176, -238, -69, -133, -50, -175, -109, -187, -2, -143, -103, -145, -39, -109, -25, -240, -191, -104, -112, -255, -149, -199, -142, -136, -98, -20, -224, -196, -6, -60, -45, -154, -10, -128, -45, -156, -182, -83, -145, -149, -235, -21, -228, -43, -166, -57, -56, -6, -66, -93, -225, -92, -171, -232, -67, -240, -46, -122, -138, -216, -163, -239, -217, -234, -127, -190, -21, -147, -247, -168, -163, -234, -197, -7, -180, -253, -56, -18, -94, -123, -153, -170, -178, -138, -246, -75, -59, -215, -127, -126, -29, -189, -211, -174, -139, -209, -212, -78, -133, -1, -123, -53, -185, -13, -198, -126, -168, -34, -54, -86, -187, -2, -15, -107, -47, -255, -243, -208, -123, -45, -225, -83, -122, -236, -136, -36, -191, -180, -39, -58, -193, -9, -82, -170, -198, -1, -213, -2, -96, -57, -224, -115, -250, -188, -109, -92, -134, -199, -16, -229, -227, -49, -68, -202, -112, -237, -233, -39, -228, -62, -4, -174, -242, -124, -126, -221, -123, -104, -113, -205, -143, -40, -255, -7, -0, -111, -78, -193, -113, -139, -170, -23, -31, -208, -214, -198, -19, -60, -162, -170, -172, -162, -253, -122, -206, -245, -87, -171, -163, -119, -218, -117, -202, -89, -135, -248, -155, -95, -142, -40, -11, -167, -33, -166, -172, -239, -214, -186, -147, -203, -6, -80, -219, -118, -90, -239, -166, -245, -10, -18, -118, -180, -56, -118, -116, -38, -195, -121, -248, -45, -2, -127, -0, -172, -27, -246, -164, -198, -30, -156, -241, -178, -23, -114, -234, -51, -15, -17, -118, -135, -33, -6, -58, -255, -139, -41, -0, -144, -237, -148, -85, -56, -119, -10, -186, -210, -244, -194, -0, -199, -55, -108, -179, -153, -14, -208, -25, -192, -5, -84, -4, -243, -40, -92, -167, -173, -0, -88, -150, -124, -105, -180, -91, -89, -89, -69, -123, -155, -171, -189, -209, -30, -145, -33, -228, -172, -139, -13, -36, -104, -10, -4, -154, -11, -211, -254, -184, -50, -4, -193, -126, -251, -99, -13, -206, -61, -84, -218, -79, -68, -188, -222, -87, -236, -24, -101, -152, -113, -45, -90, -10, -128, -203, -145, -229, -247, -54, -136, -86, -253, -162, -192, -235, -180, -18, -0, -218, -254, -121, -136, -192, -153, -137, -6, -201, -240, -149, -121, -218, -109, -133, -236, -229, -231, -18, -96, -47, -80, -104, -59, -30, -115, -214, -89, -135, -153, -218, -156, -135, -74, -31, -237, -216, -113, -113, -130, -51, -94, -135, -18, -9, -153, -60, -206, -228, -221, -52, -216, -106, -182, -189, -216, -26, -136, -59, -166, -53, -222, -88, -136, -44, -233, -206, -69, -114, -215, -85, -38, -59, -208, -9, -103, -131, -23, -238, -2, -76, -15, -184, -102, -39, -1, -160, -60, -62, -172, -60, -110, -69, -77, -138, -125, -101, -37, -215, -125, -130, -234, -116, -232, -3, -171, -3, -198, -89, -206, -58, -114, -167, -168, -187, -8, -252, -138, -16, -233, -216, -113, -113, -67, -140, -241, -218, -242, -122, -149, -136, -113, -161, -13, -200, -227, -243, -89, -239, -170, -39, -201, -205, -14, -161, -102, -201, -139, -104, -96, -207, -66, -206, -154, -207, -1, -46, -108, -112, -131, -27, -21, -202, -215, -118, -38, -217, -55, -107, -120, -216, -201, -126, -39, -185, -103, -222, -64, -89, -201, -117, -91, -61, -92, -122, -202, -89, -215, -7, -144, -92, -132, -0, -95, -105, -208, -38, -202, -177, -99, -108, -208, -62, -6, -67, -171, -140, -87, -30, -62, -222, -241, -58, -238, -1, -252, -93, -111, -236, -100, -228, -11, -7, -226, -219, -157, -33, -129, -53, -190, -10, -172, -80, -195, -99, -115, -228, -120, -100, -38, -226, -156, -83, -27, -103, -223, -247, -64, -245, -250, -54, -237, -118, -93, -86, -160, -231, -233, -75, -157, -141, -134, -127, -246, -149, -245, -1, -250, -203, -89, -119, -157, -254, -191, -113, -100, -28, -15, -191, -213, -180, -253, -156, -38, -3, -159, -8, -199, -142, -125, -128, -246, -49, -24, -26, -103, -188, -42, -225, -179, -216, -10, -0, -251, -213, -127, -174, -254, -134, -134, -58, -128, -150, -215, -29, -241, -64, -17, -205, -179, -21, -70, -103, -81, -177, -100, -101, -164, -194, -239, -163, -101, -101, -227, -9, -218, -239, -89, -136, -9, -46, -52, -136, -140, -19, -185, -31, -157, -143, -29, -27, -92, -43, -56, -47, -2, -45, -99, -48, -40, -109, -163, -140, -87, -37, -60, -22, -91, -1, -96, -131, -118, -188, -64, -127, -15, -91, -0, -172, -171, -191, -173, -179, -195, -21, -212, -156, -121, -146, -31, -249, -29, -85, -85, -54, -158, -224, -60, -143, -198, -145, -113, -122, -232, -75, -235, -99, -199, -134, -215, -105, -148, -23, -161, -227, -181, -130, -51, -94, -77, -40, -144, -107, -125, -207, -71, -210, -101, -5, -11, -0, -58, -56, -141, -56, -3, -126, -37, -242, -175, -222, -109, -132, -217, -143, -239, -133, -232, -26, -150, -169, -42, -27, -79, -112, -158, -71, -227, -200, -56, -227, -17, -52, -204, -139, -176, -56, -3, -120, -1, -146, -165, -249, -78, -68, -7, -243, -32, -178, -157, -252, -70, -9, -125, -20, -189, -134, -101, -182, -22, -185, -59, -171, -53, -105, -60, -139, -138, -104, -49, -158, -142, -180, -113, -26, -65, -7, -192, -251, -201, -131, -58, -116, -138, -36, -60, -158, -225, -8, -128, -198, -145, -113, -198, -35, -104, -144, -23, -161, -203, -135, -102, -172, -3, -9, -125, -103, -183, -225, -243, -17, -155, -11, -155, -113, -219, -235, -250, -75, -36, -189, -134, -203, -112, -5, -196, -189, -213, -77, -181, -253, -40, -146, -130, -107, -224, -40, -205, -105, -215, -58, -86, -157, -35, -0, -102, -59, -215, -236, -100, -170, -218, -7, -144, -147, -141, -63, -170, -144, -156, -141, -40, -58, -39, -35, -22, -96, -3, -251, -73, -231, -94, -26, -125, -213, -156, -118, -141, -34, -227, -180, -189, -222, -120, -2, -237, -131, -169, -142, -105, -193, -129, -164, -147, -183, -167, -71, -191, -64, -77, -168, -17, -5, -252, -22, -84, -40, -178, -137, -160, -215, -240, -49, -93, -74, -59, -243, -8, -249, -106, -224, -54, -26, -90, -63, -17, -16, -171, -206, -25, -184, -23, -0, -219, -59, -15, -98, -224, -11, -56, -90, -0, -118, -37, -95, -157, -216, -140, -173, -55, -145, -167, -151, -58, -204, -211, -166, -171, -0, -104, -27, -118, -122, -113, -22, -0, -109, -189, -19, -27, -11, -142, -182, -239, -193, -161, -9, -94, -206, -147, -39, -208, -253, -119, -248, -211, -24, -209, -62, -190, -94, -67, -59, -116, -60, -34, -157, -172, -251, -107, -35, -229, -19, -1, -177, -234, -138, -15, -26, -201, -84, -11, -226, -71, -62, -38, -220, -30, -201, -131, -78, -126, -169, -80, -190, -58, -240, -73, -60, -130, -177, -237, -132, -108, -59, -240, -34, -92, -207, -98, -158, -14, -166, -179, -245, -93, -84, -157, -194, -52, -254, -178, -182, -189, -191, -154, -123, -168, -243, -78, -108, -44, -56, -186, -244, -147, -134, -203, -121, -68, -225, -13, -99, -196, -110, -196, -24, -51, -242, -20, -0, -216, -68, -127, -151, -133, -116, -110, -29, -171, -206, -247, -160, -201, -67, -72, -159, -67, -172, -160, -135, -35, -175, -217, -52, -181, -182, -53, -138, -121, -69, -131, -107, -88, -12, -91, -0, -88, -204, -67, -38, -227, -57, -192, -158, -148, -196, -246, -115, -232, -109, -214, -224, -171, -16, -133, -156, -197, -69, -148, -164, -254, -102, -20, -190, -172, -37, -253, -104, -27, -76, -181, -84, -112, -116, -120, -15, -141, -151, -243, -228, -199, -171, -3, -17, -164, -70, -13, -218, -161, -227, -245, -255, -214, -231, -218, -151, -157, -167, -83, -172, -58, -223, -131, -70, -190, -172, -54, -208, -231, -126, -145, -239, -171, -77, -106, -109, -187, -2, -186, -140, -26, -115, -104, -167, -141, -197, -176, -5, -64, -217, -68, -190, -20, -207, -177, -170, -175, -159, -72, -210, -137, -221, -201, -183, -56, -7, -148, -244, -117, -216, -95, -214, -40, -65, -81, -29, -126, -85, -110, -205, -109, -223, -67, -227, -229, -60, -185, -123, -121, -155, -15, -76, -41, -66, -121, -89, -134, -199, -2, -219, -162, -199, -103, -202, -227, -120, -36, -20, -180, -205, -246, -251, -27, -79, -187, -78, -78, -35, -101, -15, -26, -209, -7, -44, -66, -190, -190, -47, -109, -116, -51, -229, -215, -106, -155, -90, -251, -185, -228, -89, -102, -108, -222, -185, -186, -120, -122, -22, -163, -182, -5, -96, -112, -34, -127, -183, -73, -63, -117, -76, -0, -220, -212, -240, -30, -250, -248, -178, -182, -254, -208, -208, -206, -173, -185, -109, -63, -27, -47, -231, -201, -87, -77, -219, -54, -104, -99, -97, -5, -126, -55, -7, -33, -135, -225, -28, -242, -227, -192, -69, -78, -249, -36, -60, -103, -243, -116, -116, -26, -169, -121, -208, -54, -118, -253, -77, -68, -8, -132, -64, -135, -212, -218, -200, -151, -237, -235, -140, -60, -33, -249, -95, -217, -75, -115, -104, -70, -93, -7, -64, -197, -68, -174, -105, -183, -187, -29, -19, -13, -239, -161, -143, -47, -107, -219, -96, -170, -93, -221, -154, -155, -246, -179, -241, -114, -30, -177, -244, -132, -6, -89, -172, -171, -222, -91, -43, -32, -190, -241, -127, -67, -150, -222, -118, -73, -50, -13, -56, -29, -217, -67, -150, -37, -88, -28, -147, -78, -35, -125, -1, -9, -172, -241, -254, -194, -160, -218, -217, -67, -215, -234, -5, -117, -24, -120, -165, -215, -163, -98, -34, -215, -180, -219, -77, -235, -158, -40, -233, -235, -48, -191, -172, -109, -131, -169, -182, -21, -28, -109, -251, -217, -102, -57, -127, -176, -182, -57, -191, -65, -155, -210, -247, -22, -5, -202, -252, -248, -0, -186, -49, -233, -52, -210, -55, -244, -30, -109, -16, -200, -41, -158, -250, -126, -95, -80, -131, -235, -81, -49, -145, -107, -218, -157, -160, -117, -255, -242, -212, -13, -251, -203, -218, -54, -152, -106, -91, -193, -81, -213, -207, -23, -87, -180, -107, -179, -156, -95, -151, -60, -143, -193, -33, -56, -241, -26, -145, -20, -110, -187, -86, -244, -111, -212, -5, -192, -208, -156, -70, -186, -160, -237, -192, -171, -225, -249, -82, -109, -214, -232, -203, -218, -7, -170, -174, -71, -245, -68, -30, -104, -135, -56, -84, -125, -158, -92, -119, -240, -78, -79, -187, -62, -190, -172, -85, -19, -171, -109, -48, -213, -182, -130, -195, -222, -187, -47, -252, -247, -59, -42, -218, -53, -94, -206, -107, -187, -247, -145, -219, -220, -204, -67, -156, -192, -102, -233, -239, -255, -120, -232, -75, -223, -119, -20, -40, -243, -227, -3, -105, -135, -226, -52, -210, -5, -53, -3, -175, -106, -192, -110, -128, -132, -151, -222, -160, -80, -254, -52, -114, -39, -150, -43, -43, -174, -55, -106, -2, -128, -176, -137, -108, -97, -149, -73, -215, -49, -50, -6, -196, -129, -37, -215, -107, -251, -101, -109, -59, -177, -218, -6, -83, -109, -43, -56, -172, -137, -173, -47, -252, -247, -209, -21, -237, -26, -47, -231, -157, -182, -155, -35, -185, -13, -239, -65, -182, -18, -51, -144, -147, -167, -15, -121, -104, -7, -222, -247, -98, -5, -106, -34, -215, -208, -208, -17, -194, -121, -96, -93, -194, -78, -63, -128, -104, -121, -175, -36, -15, -213, -52, -11, -216, -186, -226, -122, -195, -22, -0, -77, -39, -114, -17, -179, -144, -19, -143, -147, -80, -159, -251, -146, -118, -109, -191, -172, -173, -38, -150, -214, -55, -254, -208, -208, -94, -112, -216, -163, -204, -98, -248, -239, -247, -144, -239, -243, -125, -237, -26, -47, -231, -219, -192, -243, -222, -6, -16, -227, -58, -189, -130, -134, -185, -231, -11, -109, -27, -57, -66, -56, -207, -165, -75, -216, -233, -71, -145, -175, -198, -227, -136, -31, -192, -49, -192, -38, -53, -215, -27, -182, -0, -176, -8, -157, -200, -173, -250, -73, -251, -47, -107, -171, -137, -213, -5, -180, -19, -28, -175, -113, -238, -195, -134, -255, -182, -17, -160, -190, -133, -90, -250, -149, -92, -175, -209, -114, -190, -229, -61, -89, -196, -57, -6, -28, -13, -208, -49, -247, -60, -13, -28, -33, -156, -7, -54, -148, -164, -142, -109, -39, -214, -176, -175, -215, -161, -93, -219, -47, -107, -235, -137, -53, -108, -0, -111, -64, -220, -227, -167, -105, -191, -174, -65, -183, -60, -168, -0, -172, -104, -27, -188, -156, -111, -217, -183, -161, -142, -175, -94, -64, -156, -220, -243, -65, -142, -16, -73, -0, -196, -109, -167, -109, -219, -230, -55, -104, -61, -177, -198, -27, -144, -128, -172, -179, -233, -47, -65, -73, -52, -59, -128, -149, -129, -207, -34, -182, -223, -143, -35, -75, -151, -59, -145, -163, -174, -210, -188, -238, -5, -30, -63, -69, -246, -63, -191, -8, -164, -239, -148, -123, -190, -9, -70, -81, -0, -148, -46, -209, -208, -8, -202, -145, -175, -55, -52, -1, -144, -80, -15, -196, -106, -116, -145, -10, -188, -82, -33, -64, -190, -69, -216, -190, -170, -204, -169, -139, -46, -0, -174, -115, -152, -62, -142, -132, -8, -179, -150, -128, -15, -17, -96, -3, -79, -238, -241, -52, -51, -74, -167, -34, -162, -70, -0, -148, -30, -63, -69, -184, -94, -21, -62, -223, -195, -245, -146, -0, -24, -67, -0, -222, -77, -158, -133, -250, -218, -10, -186, -187, -138, -239, -193, -87, -230, -212, -69, -23, -0, -123, -0, -63, -193, -201, -59, -142, -36, -147, -188, -64, -47, -116, -106, -0, -143, -35, -145, -229, -78, -200, -254, -29, -90, -68, -191, -173, -153, -200, -85, -202, -188, -86, -199, -79, -139, -59, -22, -87, -1, -64, -131, -64, -163, -67, -234, -207, -165, -148, -88, -84, -106, -189, -61, -213, -90, -167, -170, -204, -169, -27, -206, -123, -3, -94, -168, -23, -122, -44, -50, -95, -104, -17, -253, -182, -131, -0, -104, -125, -252, -180, -56, -99, -49, -22, -0, -149, -129, -70, -145, -232, -87, -7, -33, -39, -37, -115, -144, -237, -238, -161, -212, -132, -190, -111, -216, -135, -157, -145, -21, -192, -1, -122, -141, -210, -108, -201, -72, -252, -5, -112, -76, -238, -125, -101, -78, -221, -208, -4, -192, -202, -118, -178, -70, -230, -107, -209, -40, -250, -109, -7, -1, -48, -244, -227, -167, -197, -25, -250, -184, -46, -215, -255, -175, -173, -19, -238, -110, -68, -195, -127, -123, -69, -187, -224, -88, -12, -200, -10, -244, -100, -100, -85, -248, -36, -240, -59, -2, -130, -196, -16, -16, -104, -84, -199, -195, -34, -36, -120, -201, -151, -201, -147, -167, -252, -182, -142, -127, -40, -144, -143, -217, -124, -228, -68, -228, -103, -84, -56, -180, -33, -39, -6, -211, -234, -202, -156, -58, -139, -126, -143, -1, -17, -141, -45, -212, -36, -208, -68, -206, -203, -33, -48, -96, -165, -115, -3, -141, -162, -223, -118, -16, -0, -227, -230, -248, -105, -60, -64, -159, -219, -61, -72, -26, -180, -41, -250, -123, -33, -162, -60, -190, -180, -164, -77, -112, -44, -6, -157, -252, -54, -83, -213, -249, -72, -128, -218, -121, -200, -74, -174, -82, -8, -16, -16, -104, -20, -73, -152, -242, -170, -66, -217, -121, -68, -254, -208, -133, -2, -241, -124, -189, -163, -174, -204, -169, -171, -69, -140, -78, -173, -64, -238, -240, -241, -165, -26, -218, -87, -148, -77, -218, -154, -27, -104, -20, -253, -214, -105, -215, -38, -160, -68, -47, -199, -79, -180, -76, -87, -165, -180, -65, -95, -68, -242, -56, -141, -62, -188, -172, -77, -191, -187, -64, -175, -59, -31, -17, -164, -255, -3, -222, -132, -38, -74, -197, -241, -11, -112, -232, -27, -197, -98, -64, -143, -17, -113, -50, -60, -35, -66, -124, -62, -112, -82, -79, -247, -116, -61, -48, -169, -15, -222, -227, -14, -72, -162, -208, -203, -245, -37, -92, -74, -69, -22, -92, -196, -2, -236, -159, -74, -91, -155, -161, -69, -219, -88, -180, -141, -126, -59, -148, -227, -188, -16, -208, -62, -93, -85, -163, -232, -68, -72, -172, -185, -67, -157, -103, -240, -123, -224, -67, -68, -136, -149, -208, -20, -78, -31, -30, -194, -217, -82, -85, -208, -55, -138, -197, -160, -207, -99, -96, -43, -1, -252, -133, -10, -101, -90, -91, -32, -62, -19, -139, -128, -157, -98, -243, -30, -119, -64, -210, -45, -93, -175, -47, -248, -106, -52, -174, -89, -5, -189, -85, -162, -205, -2, -158, -31, -120, -141, -86, -19, -121, -140, -10, -128, -198, -233, -170, -104, -25, -157, -72, -219, -90, -84, -42, -128, -60, -253, -170, -92, -153, -208, -192, -191, -194, -233, -195, -103, -171, -232, -218, -66, -251, -112, -139, -167, -252, -12, -60, -251, -98, -58, -172, -144, -144, -192, -174, -243, -129, -61, -99, -222, -195, -184, -5, -18, -204, -16, -224, -6, -106, -164, -59, -146, -4, -19, -100, -73, -61, -96, -176, -80, -209, -174, -15, -1, -16, -253, -60, -191, -47, -208, -45, -58, -81, -168, -0, -104, -180, -50, -161, -129, -127, -133, -211, -135, -94, -146, -175, -146, -7, -134, -117, -21, -196, -47, -71, -156, -144, -188, -71, -210, -180, -88, -33, -1, -223, -70, -20, -140, -111, -9, -236, -215, -146, -218, -230, -38, -196, -232, -237, -33, -42, -182, -122, -228, -130, -125, -131, -50, -154, -49, -7, -242, -176, -87, -219, -212, -208, -109, -133, -44, -155, -230, -1, -219, -53, -188, -70, -91, -1, -48, -225, -207, -243, -27, -8, -128, -54, -43, -147, -32, -255, -138, -170, -247, -87, -66, -223, -232, -11, -13, -172, -135, -248, -22, -44, -2, -206, -5, -206, -68, -4, -218, -163, -192, -179, -107, -174, -85, -251, -124, -116, -34, -255, -82, -39, -114, -208, -170, -85, -219, -125, -67, -121, -47, -66, -244, -70, -183, -0, -191, -175, -160, -183, -31, -211, -198, -201, -68, -201, -243, -100, -222, -83, -65, -211, -40, -194, -117, -232, -133, -237, -36, -27, -80, -230, -20, -232, -78, -87, -186, -111, -182, -184, -70, -233, -0, -98, -140, -156, -231, -59, -125, -44, -53, -89, -174, -24, -212, -79, -33, -70, -95, -74, -174, -217, -203, -25, -48, -1, -254, -21, -85, -239, -175, -162, -77, -163, -47, -52, -34, -4, -78, -35, -215, -145, -156, -74, -128, -89, -122, -200, -243, -65, -182, -18, -51, -129, -111, -34, -251, -127, -247, -95, -105, -106, -58, -242, -120, -153, -95, -112, -202, -202, -66, -174, -175, -134, -156, -70, -52, -122, -78, -78, -251, -255, -105, -91, -111, -162, -91, -90, -68, -184, -142, -10, -231, -230, -26, -101, -12, -210, -182, -165, -3, -136, -49, -114, -158, -239, -244, -177, -106, -9, -105, -17, -124, -30, -75, -0, -2, -251, -53, -106, -198, -59, -85, -239, -175, -65, -219, -190, -4, -88, -45, -255, -154, -199, -255, -137, -138, -118, -214, -205, -183, -46, -148, -252, -81, -228, -113, -1, -138, -152, -26, -112, -15, -171, -146, -155, -226, -15, -196, -23, -164, -131, -14, -201, -69, -87, -103, -28, -59, -1, -163, -90, -9, -214, -224, -96, -99, -204, -91, -141, -49, -47, -55, -198, -220, -13, -220, -108, -140, -89, -195, -24, -179, -158, -49, -230, -219, -198, -152, -47, -27, -99, -158, -214, -150, -57, -34, -205, -247, -51, -198, -236, -229, -20, -79, -7, -238, -50, -198, -252, -57, -203, -178, -50, -251, -253, -221, -179, -44, -187, -174, -225, -229, -174, -51, -198, -244, -226, -55, -161, -247, -49, -191, -164, -250, -229, -89, -150, -85, -234, -25, -22, -119, -100, -89, -214, -56, -225, -12, -146, -164, -198, -158, -134, -205, -171, -33, -127, -194, -24, -115, -184, -49, -102, -127, -253, -253, -75, -45, -51, -198, -152, -233, -1, -151, -219, -198, -24, -147, -25, -99, -174, -206, -178, -204, -103, -202, -252, -87, -173, -223, -42, -203, -178, -73, -218, -191, -173, -141, -49, -167, -107, -93, -187, -163, -97, -100, -95, -52, -27, -56, -60, -128, -246, -30, -149, -80, -3, -38, -187, -1, -109, -75, -191, -32, -140, -210, -121, -190, -182, -63, -217, -233, -219, -44, -196, -168, -228, -94, -253, -253, -231, -138, -251, -8, -254, -154, -181, -105, -211, -166, -61, -13, -150, -220, -52, -223, -163, -151, -190, -191, -88, -253, -111, -139, -62, -248, -35, -123, -255, -219, -125, -15, -39, -176, -47, -141, -158, -19, -98, -57, -8, -145, -34, -8, -53, -185, -176, -117, -65, -172, -61, -107, -69, -156, -127, -64, -38, -224, -250, -78, -249, -166, -136, -107, -240, -238, -189, -118, -54, -50, -144, -100, -36, -22, -135, -224, -164, -196, -2, -214, -199, -227, -206, -217, -102, -176, -117, -29, -160, -77, -219, -135, -210, -211, -76, -96, -180, -26, -216, -109, -250, -63, -22, -248, -3, -63, -208, -127, -22, -71, -217, -178, -192, -190, -52, -21, -0, -183, -234, -191, -202, -208, -120, -209, -65, -179, -21, -192, -218, -136, -253, -183, -197, -253, -228, -81, -97, -64, -189, -253, -198, -11, -200, -163, -231, -94, -220, -160, -77, -227, -193, -214, -117, -128, -54, -109, -223, -7, -253, -48, -5, -0, -13, -61, -251, -186, -62, -223, -64, -222, -161, -167, -31, -173, -159, -83, -95, -125, -114, -177, -68, -177, -32, -203, -178, -189, -179, -44, -123, -122, -150, -101, -159, -169, -107, -156, -101, -217, -84, -35, -123, -141, -195, -141, -49, -83, -140, -236, -197, -151, -48, -198, -92, -108, -140, -249, -156, -49, -102, -219, -166, -29, -26, -101, -88, -101, -75, -173, -9, -239, -68, -71, -150, -227, -182, -33, -92, -238, -0, -99, -204, -242, -198, -152, -169, -198, -152, -104, -65, -84, -18, -186, -43, -1, -77, -150, -101, -15, -25, -99, -62, -171, -255, -198, -59, -236, -17, -211, -141, -45, -218, -94, -75, -201, -118, -176, -141, -194, -41, -65, -128, -216, -34, -124, -220, -136, -2, -109, -199, -44, -203, -238, -26, -229, -46, -45, -86, -24, -90, -72, -174, -50, -32, -249, -6, -239, -48, -34, -225, -175, -204, -178, -204, -119, -228, -81, -171, -212, -43, -78, -178, -54, -109, -140, -49, -118, -207, -239, -77, -131, -94, -131, -54, -26, -253, -36, -52, -234, -241, -27, -99, -204, -195, -198, -152, -183, -182, -56, -101, -73, -168, -193, -168, -11, -0, -211, -108, -121, -215, -102, -146, -53, -105, -51, -199, -136, -16, -88, -174, -142, -208, -131, -49, -117, -12, -184, -184, -32, -203, -178, -168, -193, -52, -19, -2, -129, -132, -8, -251, -15, -240, -120, -143, -215, -168, -13, -220, -160, -116, -67, -81, -180, -33, -102, -179, -0, -123, -213, -83, -15, -183, -111, -218, -238, -29, -12, -106, -233, -63, -136, -115, -90, -17, -227, -122, -125, -42, -209, -198, -59, -255, -166, -10, -55, -52, -206, -4, -145, -82, -220, -199, -232, -147, -139, -1, -37, -160, -50, -220, -223, -24, -243, -43, -99, -204, -43, -141, -49, -247, -55, -232, -64, -211, -7, -110, -151, -119, -219, -141, -145, -229, -221, -213, -250, -119, -151, -74, -170, -81, -0, -98, -216, -115, -134, -49, -230, -11, -78, -241, -251, -141, -49, -39, -25, -99, -134, -103, -254, -153, -208, -20, -183, -234, -223, -49, -55, -166, -140, -41, -17, -0, -198, -152, -125, -245, -239, -39, -178, -44, -123, -97, -9, -77, -103, -100, -89, -182, -105, -150, -101, -107, -103, -89, -118, -117, -61, -245, -80, -96, -195, -65, -189, -17, -248, -46, -35, -227, -179, -173, -5, -188, -125, -148, -250, -101, -178, -44, -91, -144, -149, -99, -66, -91, -245, -89, -216, -21, -146, -83, -180, -127, -200, -10, -169, -103, -28, -235, -244, -229, -102, -196, -132, -189, -214, -20, -120, -84, -225, -124, -209, -155, -250, -246, -143, -153, -37, -93, -219, -62, -145, -59, -56, -129, -156, -61, -223, -64, -30, -154, -42, -74, -150, -214, -190, -159, -87, -215, -235, -141, -197, -247, -25, -192, -115, -40, -17, -147, -28, -158, -161, -91, -128, -12, -216, -95, -199, -209, -108, -196, -122, -213, -27, -50, -109, -88, -125, -114, -81, -167, -4, -12, -138, -239, -215, -4, -208, -74, -59, -63, -76, -236, -98, -140, -249, -146, -49, -102, -79, -99, -204, -250, -198, -24, -171, -132, -186, -195, -24, -83, -26, -213, -53, -161, -30, -192, -59, -140, -49, -110, -34, -213, -253, -129, -179, -140, -49, -167, -103, -89, -214, -41, -22, -95, -150, -101, -11, -140, -216, -198, -143, -41, -100, -89, -134, -17, -255, -149, -131, -71, -187, -47, -62, -140, -230, -41, -192, -176, -142, -205, -26, -181, -201, -178, -108, -190, -49, -230, -123, -198, -152, -239, -57, -194, -106, -165, -44, -203, -234, -28, -56, -142, -163, -36, -33, -74, -150, -101, -91, -213, -180, -93, -236, -65, -174, -195, -112, -241, -126, -253, -247, -114, -99, -76, -218, -198, -140, -2, -70, -83, -0, -12, -235, -216, -44, -198, -81, -91, -101, -108, -63, -69, -155, -229, -236, -132, -17, -26, -99, -245, -11, -221, -20, -139, -155, -125, -198, -128, -0, -96, -100, -18, -133, -97, -186, -249, -134, -160, -141, -208, -104, -211, -198, -24, -19, -246, -178, -59, -14, -136, -94, -117, -0, -125, -46, -185, -19, -22, -51, -0, -203, -3, -47, -3, -254, -165, -10, -133, -191, -53, -104, -27, -172, -212, -25, -150, -210, -172, -111, -69, -214, -88, -71, -27, -165, -24, -45, -237, -12, -198, -2, -144, -0, -26, -199, -106, -191, -79, -243, -212, -15, -96, -52, -250, -233, -244, -103, -105, -228, -68, -224, -78, -26, -4, -240, -232, -179, -67, -22, -55, -2, -95, -1, -130, -131, -106, -36, -1, -48, -254, -209, -70, -96, -140, -21, -0, -239, -69, -162, -83, -221, -133, -68, -225, -169, -18, -0, -87, -17, -51, -115, -78, -75, -0, -27, -58, -125, -26, -181, -227, -101, -215, -14, -224, -127, -70, -246, -202, -235, -25, -99, -94, -104, -196, -60, -55, -97, -130, -160, -15, -59, -3, -224, -37, -200, -209, -151, -215, -156, -151, -6, -97, -200, -107, -176, -185, -49, -230, -95, -198, -152, -16, -107, -187, -247, -103, -89, -182, -149, -79, -199, -226, -147, -124, -109, -251, -76, -189, -11, -243, -221, -198, -152, -43, -141, -49, -119, -26, -99, -46, -15, -232, -119, -255, -0, -86, -68, -242, -145, -1, -252, -179, -65, -187, -224, -175, -109, -197, -87, -166, -234, -161, -7, -243, -239, -210, -70, -219, -5, -199, -210, -71, -76, -152, -255, -3, -236, -209, -228, -26, -19, -5, -192, -31, -144, -184, -118, -231, -251, -132, -0, -13, -194, -144, -55, -184, -38, -84, -175, -0, -74, -207, -202, -201, -227, -56, -218, -73, -238, -27, -139, -65, -125, -166, -38, -57, -233, -152, -5, -176, -142, -243, -176, -86, -10, -108, -19, -60, -217, -28, -218, -54, -65, -52, -135, -33, -0, -130, -99, -233, -147, -39, -79, -1, -248, -98, -13, -223, -37, -144, -40, -67, -143, -35, -65, -83, -14, -68, -98, -204, -21, -233, -62, -15, -92, -134, -132, -230, -126, -20, -248, -59, -240, -188, -38, -247, -48, -86, -128, -100, -198, -61, -205, -190, -239, -18, -154, -160, -48, -228, -13, -174, -9, -45, -5, -128, -67, -91, -23, -146, -174, -178, -207, -4, -250, -184, -116, -5, -18, -54, -124, -54, -240, -75, -79, -221, -47, -144, -237, -208, -79, -219, -48, -182, -216, -176, -33, -125, -223, -58, -128, -94, -133, -134, -182, -107, -20, -75, -31, -201, -42, -3, -240, -64, -13, -223, -47, -34, -81, -139, -191, -160, -255, -230, -227, -137, -62, -139, -164, -146, -62, -20, -120, -63, -176, -59, -146, -120, -226, -162, -10, -190, -149, -202, -36, -74, -80, -255, -36, -226, -1, -73, -45, -87, -26, -98, -142, -128, -48, -228, -13, -174, -5, -61, -11, -128, -186, -62, -19, -144, -156, -180, -105, -159, -74, -218, -219, -240, -125, -3, -238, -235, -78, -221, -147, -109, -24, -55, -234, -88, -147, -201, -214, -132, -214, -211, -166, -20, -49, -174, -211, -6, -192, -243, -235, -6, -139, -210, -93, -11, -252, -213, -249, -253, -87, -60, -166, -197, -158, -118, -135, -81, -145, -177, -150, -26, -101, -18, -178, -162, -25, -248, -231, -161, -179, -193, -80, -127, -94, -211, -159, -208, -231, -191, -51, -178, -2, -56, -64, -175, -57, -20, -43, -74, -237, -78, -239, -2, -32, -6, -154, -244, -169, -164, -253, -145, -200, -10, -224, -23, -158, -58, -187, -58, -56, -178, -247, -142, -53, -153, -108, -195, -154, -152, -195, -66, -232, -96, -65, -210, -79, -29, -230, -252, -62, -12, -24, -176, -179, -64, -146, -178, -254, -12, -177, -29, -159, -129, -198, -134, -175, -224, -107, -87, -0, -141, -226, -193, -123, -248, -172, -6, -220, -167, -183, -242, -158, -10, -186, -80, -129, -50, -3, -89, -229, -76, -209, -251, -25, -138, -82, -121, -34, -9, -128, -24, -24, -11, -1, -65, -38, -10, -22, -153, -145, -150, -112, -101, -158, -152, -191, -49, -18, -19, -254, -48, -99, -204, -100, -99, -204, -206, -250, -207, -11, -53, -93, -30, -136, -162, -212, -20, -89, -150, -61, -134, -164, -150, -250, -147, -49, -230, -112, -224, -239, -89, -150, -13, -88, -41, -102, -89, -22, -116, -60, -156, -101, -217, -10, -93, -251, -52, -150, -225, -8, -135, -141, -139, -113, -17, -171, -234, -198, -26, -202, -6, -161, -117, -2, -170, -76, -13, -150, -208, -8, -183, -24, -99, -54, -113, -126, -63, -207, -228, -190, -226, -46, -182, -54, -198, -28, -159, -101, -217, -215, -178, -44, -251, -189, -49, -102, -152, -97, -161, -207, -48, -198, -220, -100, -140, -89, -219, -24, -19, -39, -207, -92, -194, -168, -0, -248, -152, -174, -46, -94, -92, -69, -87, -38, -0, -172, -212, -122, -83, -192, -133, -92, -30, -115, -3, -251, -183, -56, -225, -169, -136, -73, -192, -186, -21, -116, -71, -27, -99, -182, -3, -246, -3, -246, -51, -198, -108, -103, -114, -95, -113, -23, -119, -24, -99, -182, -69, -148, -128, -223, -51, -198, -188, -173, -174, -3, -177, -150, -146, -234, -185, -102, -253, -233, -247, -41, -188, -219, -113, -1, -181, -91, -40, -221, -194, -76, -32, -172, -84, -248, -27, -14, -196, -178, -202, -226, -250, -26, -218, -245, -148, -110, -33, -1, -199, -134, -14, -223, -96, -141, -126, -0, -207, -159, -43, -207, -63, -4, -208, -238, -66, -158, -179, -109, -149, -166, -215, -42, -225, -121, -182, -242, -59, -23, -209, -14, -15, -152, -206, -34, -126, -225, -223, -36, -63, -6, -252, -14, -254, -99, -192, -215, -144, -251, -142, -159, -13, -236, -90, -183, -23, -141, -185, -151, -68, -108, -65, -108, -142, -197, -215, -117, -229, -55, -86, -208, -228, -25, -17, -118, -10, -80, -202, -47, -244, -90, -49, -223, -155, -135, -247, -55, -149, -119, -80, -218, -115, -31, -131, -119, -2, -23, -227, -81, -84, -57, -52, -75, -3, -39, -233, -133, -74, -143, -170, -10, -109, -106, -209, -176, -159, -59, -107, -179, -251, -129, -213, -43, -232, -50, -68, -35, -237, -34, -150, -0, -120, -58, -98, -62, -125, -99, -155, -123, -136, -112, -253, -168, -3, -9, -248, -183, -242, -59, -48, -6, -191, -177, -128, -38, -207, -136, -197, -67, -0, -28, -162, -188, -223, -87, -69, -87, -170, -4, -204, -178, -236, -12, -51, -232, -191, -109, -153, -223, -103, -140, -185, -215, -136, -217, -240, -218, -70, -162, -233, -238, -23, -210, -177, -152, -238, -148, -72, -250, -114, -155, -193, -232, -179, -89, -150, -61, -90, -66, -183, -188, -145, -96, -30, -239, -50, -18, -247, -111, -99, -211, -102, -105, -84, -142, -21, -140, -49, -47, -50, -242, -60, -102, -26, -89, -198, -143, -103, -156, -103, -140, -121, -181, -49, -166, -242, -28, -59, -97, -76, -35, -104, -11, -208, -118, -143, -55, -199, -136, -221, -245, -211, -140, -49, -103, -25, -99, -94, -155, -249, -51, -152, -246, -141, -221, -141, -49, -207, -52, -198, -220, -108, -140, -249, -99, -5, -221, -95, -140, -76, -254, -11, -141, -236, -189, -99, -251, -116, -159, -98, -140, -249, -128, -17, -13, -250, -218, -89, -150, -85, -42, -94, -198, -1, -172, -0, -171, -210, -105, -36, -140, -50, -144, -179, -126, -128, -123, -60, -213, -43, -234, -223, -74, -1, -208, -234, -24, -48, -203, -178, -32, -11, -193, -62, -129, -36, -77, -180, -230, -183, -135, -168, -2, -171, -12, -63, -54, -198, -220, -99, -140, -249, -120, -150, -101, -243, -104, -224, -233, -24, -208, -143, -149, -141, -49, -175, -215, -159, -95, -201, -178, -172, -77, -82, -145, -177, -134, -135, -244, -239, -26, -163, -218, -139, -132, -58, -188, -74, -255, -254, -213, -83, -103, -39, -254, -138, -158, -186, -167, -48, -158, -237, -0, -94, -105, -140, -217, -192, -200, -145, -165, -215, -76, -215, -34, -203, -178, -179, -140, -172, -84, -44, -150, -46, -163, -109, -129, -167, -38, -73, -150, -101, -247, -70, -228, -59, -154, -120, -68, -255, -174, -54, -170, -189, -72, -40, -5, -176, -170, -17, -175, -93, -99, -140, -249, -181, -135, -164, -217, -22, -0, -152, -171, -203, -137, -168, -174, -137, -52, -243, -174, -219, -20, -201, -208, -123, -175, -246, -103, -10, -146, -173, -216, -103, -225, -182, -173, -254, -189, -34, -32, -94, -95, -159, -88, -172, -66, -68, -41, -150, -209, -191, -211, -70, -181, -23, -9, -85, -216, -198, -200, -216, -187, -186, -100, -251, -221, -120, -11, -48, -221, -24, -179, -186, -137, -255, -210, -223, -102, -140, -89, -214, -136, -146, -236, -82, -35, -82, -235, -185, -70, -246, -238, -69, -28, -98, -196, -16, -230, -116, -35, -6, -41, -27, -26, -73, -12, -185, -145, -49, -230, -141, -5, -218, -109, -244, -239, -121, -145, -251, -155, -144, -191, -155, -135, -71, -181, -23, -139, -57, -58, -42, -196, -223, -160, -127, -15, -45, -169, -111, -188, -5, -152, -102, -74, -4, -0, -226, -110, -248, -97, -99, -204, -113, -89, -150, -237, -19, -90, -167, -56, -203, -72, -236, -187, -201, -250, -123, -178, -145, -32, -8, -87, -122, -104, -63, -100, -228, -185, -60, -117, -244, -8, -60, -215, -248, -77, -93, -173, -143, -245, -120, -215, -184, -143, -69, -88, -247, -227, -74, -15, -199, -113, -138, -147, -81, -223, -133, -113, -30, -120, -117, -71, -35, -6, -123, -167, -150, -212, -55, -51, -4, -2, -174, -211, -45, -192, -241, -158, -186, -16, -183, -195, -168, -202, -47, -224, -105, -192, -235, -17, -191, -235, -223, -122, -234, -109, -178, -142, -183, -182, -224, -109, -209, -217, -14, -0, -216, -76, -121, -205, -235, -202, -171, -67, -31, -162, -158, -39, -3, -103, -41, -191, -178, -175, -203, -184, -3, -30, -84, -208, -14, -197, -14, -160, -79, -56, -91, -250, -202, -36, -36, -197, -45, -128, -49, -254, -45, -192, -137, -70, -190, -242, -191, -241, -212, -157, -96, -140, -249, -168, -254, -141, -130, -194, -131, -63, -215, -24, -227, -139, -184, -99, -39, -175, -247, -236, -127, -136, -216, -65, -255, -250, -236, -250, -199, -29, -144, -19, -146, -109, -245, -231, -57, -163, -216, -149, -168, -8, -89, -110, -147, -91, -161, -214, -158, -18, -85, -241, -27, -11, -161, -195, -179, -44, -91, -182, -158, -202, -1, -226, -159, -14, -61, -89, -127, -53, -145, -138, -192, -14, -136, -133, -223, -17, -218, -230, -32, -15, -205, -227, -90, -183, -181, -143, -71, -96, -95, -58, -173, -0, -16, -147, -93, -139, -82, -143, -189, -190, -17, -243, -139, -131, -248, -32, -128, -132, -189, -90, -166, -190, -197, -226, -131, -38, -171, -132, -197, -14, -72, -8, -104, -144, -204, -192, -125, -240, -111, -53, -72, -129, -11, -241, -68, -147, -1, -110, -85, -126, -141, -179, -174, -70, -20, -0, -143, -33, -230, -210, -239, -236, -194, -167, -43, -34, -11, -128, -75, -149, -215, -119, -99, -244, -45, -97, -108, -35, -116, -11, -48, -20, -0, -171, -100, -89, -246, -132, -243, -123, -73, -99, -204, -51, -140, -63, -71, -225, -253, -70, -78, -7, -188, -17, -103, -135, -129, -44, -203, -22, -171, -115, -114, -196, -113, -228, -53, -70, -18, -194, -44, -54, -251, -255, -132, -114, -184, -166, -192, -211, -10, -127, -71, -3, -83, -129, -51, -144, -212, -220, -95, -55, -198, -92, -96, -100, -130, -255, -202, -67, -107, -247, -107, -111, -240, -212, -77, -24, -48, -210, -243, -176, -52, -238, -94, -0, -159, -149, -141, -49, -71, -233, -207, -207, -186, -130, -56, -97, -2, -128, -220, -125, -176, -151, -36, -5, -33, -203, -84, -224, -32, -196, -163, -110, -38, -18, -106, -106, -50, -18, -76, -115, -32, -40, -6, -240, -70, -229, -55, -151, -134, -153, -107, -98, -109, -1, -198, -2, -128, -183, -235, -189, -60, -94, -79, -93, -201, -199, -134, -131, -111, -30, -67, -46, -97, -252, -3, -216, -87, -7, -192, -182, -61, -241, -143, -182, -79, -85, -126, -203, -34, -145, -87, -1, -118, -107, -217, -151, -113, -43, -0, -128, -187, -145, -248, -245, -243, -244, -94, -14, -24, -237, -62, -37, -140, -99, -144, -135, -16, -10, -201, -174, -210, -134, -127, -84, -1, -160, -60, -247, -87, -158, -147, -98, -241, -28, -47, -64, -148, -160, -243, -144, -64, -158, -95, -247, -173, -146, -18, -18, -198, -12, -122, -18, -0, -43, -34, -201, -51, -0, -106, -67, -103, -37, -36, -36, -140, -18, -250, -16, -0, -202, -247, -67, -202, -247, -238, -241, -188, -164, -79, -72, -24, -85, -32, -94, -119, -179, -129, -195, -235, -169, -91, -241, -15, -22, -0, -84, -164, -60, -42, -161, -63, -82, -121, -255, -174, -123, -79, -19, -18, -38, -32, -200, -109, -250, -123, -57, -254, -105, -40, -0, -122, -241, -47, -72, -72, -72, -24, -9, -215, -14, -224, -68, -35, -161, -190, -134, -146, -194, -169, -6, -39, -24, -233, -75, -52, -255, -130, -132, -132, -132, -81, -68, -95, -58, -128, -132, -132, -132, -132, -132, -132, -132, -137, -0, -93, -69, -148, -165, -3, -111, -156, -84, -36, -33, -97, -34, -99, -220, -165, -126, -74, -72, -72, -136, -135, -36, -0, -18, -18, -38, -48, -122, -21, -0, -85, -138, -63, -231, -168, -111, -251, -62, -251, -144, -144, -144, -80, -142, -209, -92, -1, -60, -92, -248, -155, -144, -144, -48, -100, -140, -5, -1, -240, -72, -177, -194, -177, -74, -60, -98, -200, -125, -74, -72, -152, -80, -24, -77, -1, -96, -39, -190, -111, -5, -176, -155, -145, -192, -140, -31, -30, -94, -119, -18, -18, -38, -30, -70, -123, -5, -48, -61, -203, -178, -185, -158, -58, -107, -149, -232, -139, -66, -156, -144, -144, -16, -9, -163, -153, -27, -240, -97, -227, -89, -254, -27, -99, -76, -150, -101, -123, -27, -99, -246, -30, -110, -119, -18, -18, -18, -162, -98, -152, -230, -191, -201, -16, -40, -33, -161, -57, -146, -29, -64, -66, -194, -4, -70, -18, -0, -9, -9, -19, -24, -73, -0, -36, -36, -76, -96, -36, -1, -144, -144, -48, -129, -145, -4, -64, -66, -194, -4, -198, -184, -19, -0, -73, -219, -159, -144, -16, -15, -227, -78, -0, -36, -36, -36, -196, -67, -18, -0, -9, -9, -19, -24, -73, -0, -36, -36, -76, -96, -36, -1, -144, -144, -48, -117, -14, -169, -49, -0, -0, -2, -74, -73, -68, -65, -84, -129, -145, -4, -64, -66, -194, -4, -70, -175, -206, -64, -89, -150, -101, -125, -242, -79, -72, -72, -232, -134, -180, -2, -72, -72, -152, -192, -72, -2, -32, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -33, -225, -255, -219, -131, -3, -2, -0, -0, -0, -0, -33, -253, -95, -221, -17, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -208, -17, -112, -180, -144, -16, -164, -2, -78, -0, -0, -0, -0, -73, -69, -78, -68, -174, -66, -96, -130, -}; -/* clang-format on */ diff --git a/scene/resources/default_theme/font_lodpi.inc b/scene/resources/default_theme/font_lodpi.inc deleted file mode 100644 index d2f5851224..0000000000 --- a/scene/resources/default_theme/font_lodpi.inc +++ /dev/null @@ -1,13117 +0,0 @@ -/* clang-format off */ -static const int _lodpi_font_height=14; -static const int _lodpi_font_ascent=11; -static const int _lodpi_font_charcount=191; -static const int _lodpi_font_charrects[191][8]={ -/* charidx , ofs_x, ofs_y, size_x, size_y, valign, halign, advance */ -{64,72,34,10,11,1,1,12}, -{224,85,180,5,11,0,1,7}, -{192,32,16,11,13,-2,-1,9}, -{96,2,216,3,2,0,3,8}, -{160,0,0,0,0,11,0,4}, -{32,0,0,0,0,11,0,4}, -{33,65,234,2,10,1,1,4}, -{225,112,169,5,11,0,1,7}, -{193,17,16,11,13,-2,-1,9}, -{161,2,222,2,11,3,1,4}, -{65,2,16,11,10,1,-1,9}, -{97,76,188,5,8,3,1,7}, -{98,102,165,6,11,0,1,8}, -{226,72,143,6,11,0,1,7}, -{194,113,2,11,13,-2,-1,9}, -{66,46,109,7,10,1,1,9}, -{162,12,136,6,10,1,1,8}, -{34,49,187,5,4,1,1,6}, -{35,78,66,8,10,1,0,9}, -{163,22,167,6,10,1,1,8}, -{195,53,2,11,14,-3,-1,9}, -{227,2,155,6,12,-1,1,7}, -{67,68,115,7,10,1,1,8}, -{99,40,179,5,8,3,1,7}, -{228,121,169,5,11,0,1,7}, -{196,98,2,11,13,-2,-1,9}, -{36,102,137,6,12,0,1,8}, -{100,82,150,6,11,0,1,8}, -{68,90,66,8,10,1,1,10}, -{164,14,79,8,7,3,0,8}, -{37,2,30,10,10,1,1,12}, -{69,29,191,5,10,1,1,7}, -{165,79,98,7,10,1,0,8}, -{229,20,196,5,12,-1,1,7}, -{197,83,2,11,12,-1,-1,9}, -{101,32,124,6,8,3,1,8}, -{38,67,49,9,10,1,1,10}, -{70,101,105,6,10,1,1,7}, -{198,21,2,12,10,1,-1,12}, -{166,95,228,2,14,0,3,7}, -{102,2,201,5,11,0,0,4}, -{230,58,34,10,8,3,1,12}, -{71,66,65,8,10,1,1,10}, -{231,2,186,5,11,3,1,7}, -{199,57,97,7,13,1,1,8}, -{103,13,107,7,11,3,1,7}, -{167,112,131,6,11,0,0,7}, -{39,119,219,2,4,1,1,3}, -{72,54,65,8,10,1,1,10}, -{232,62,143,6,11,0,1,8}, -{200,47,195,5,13,-2,1,7}, -{40,93,212,4,12,1,1,4}, -{104,72,158,6,11,0,1,8}, -{168,77,217,4,2,0,2,8}, -{73,38,191,5,10,1,0,5}, -{169,44,34,10,10,1,1,12}, -{233,52,142,6,11,0,1,8}, -{201,56,197,5,13,-2,1,7}, -{41,51,226,3,12,1,0,4}, -{105,109,213,3,11,0,0,4}, -{106,101,213,4,14,0,-1,4}, -{74,92,195,5,13,1,-2,3}, -{202,65,202,5,13,-2,1,7}, -{42,108,80,7,6,0,0,8}, -{170,29,205,4,5,1,0,5}, -{234,12,181,6,11,0,1,8}, -{171,22,181,5,6,4,1,7}, -{43,101,94,7,7,3,0,8}, -{107,112,92,7,11,0,1,7}, -{203,83,200,5,13,-2,1,7}, -{235,2,171,6,11,0,1,8}, -{75,102,66,8,10,1,1,8}, -{44,107,231,2,3,9,1,4}, -{172,2,104,7,4,6,0,8}, -{108,113,228,2,11,0,1,4}, -{204,101,196,5,13,-2,0,5}, -{236,30,214,3,11,0,0,4}, -{76,22,124,6,10,1,1,7}, -{173,16,229,3,2,7,1,5}, -{45,123,201,3,2,7,1,5}, -{109,68,2,11,8,3,1,13}, -{205,11,211,5,13,-2,0,5}, -{237,37,214,3,11,0,1,4}, -{77,62,20,10,10,1,1,12}, -{46,101,231,2,2,9,1,4}, -{110,111,107,6,8,3,1,8}, -{206,20,212,5,13,-2,0,5}, -{238,11,196,5,11,0,-1,4}, -{174,30,33,10,10,1,1,12}, -{78,2,79,8,10,1,1,10}, -{175,35,111,7,1,-1,0,7}, -{111,102,153,6,8,3,1,8}, -{207,119,184,5,13,-2,0,5}, -{239,69,219,4,11,0,0,4}, -{79,41,66,9,10,1,1,11}, -{47,90,105,7,10,1,-1,5}, -{176,61,219,4,4,1,1,6}, -{112,32,150,6,11,3,1,8}, -{240,82,165,6,11,0,1,8}, -{208,86,33,9,10,1,0,10}, -{80,52,128,6,10,1,1,8}, -{48,42,135,6,10,1,1,8}, -{177,46,97,7,8,3,0,8}, -{113,22,152,6,11,3,1,8}, -{241,2,112,6,12,-1,1,8}, -{81,15,59,9,13,1,1,11}, -{209,74,80,8,14,-3,1,10}, -{49,45,212,4,10,1,2,8}, -{178,58,187,5,6,1,0,5}, -{114,85,217,4,8,3,1,5}, -{210,2,62,9,13,-2,1,11}, -{242,62,165,6,11,0,1,8}, -{82,35,97,7,10,1,1,8}, -{50,57,114,7,10,1,1,8}, -{179,53,214,4,6,1,0,5}, -{115,112,146,6,8,3,0,7}, -{211,106,49,9,13,-2,1,11}, -{243,52,172,6,11,0,1,8}, -{83,24,96,7,10,1,0,7}, -{51,22,138,6,10,1,1,8}, -{180,9,228,3,2,0,3,8}, -{116,67,188,5,10,1,0,5}, -{212,93,49,9,13,-2,1,11}, -{244,42,164,6,11,0,1,8}, -{84,13,93,7,10,1,0,7}, -{52,24,110,7,10,1,1,8}, -{53,92,119,6,10,1,1,8}, -{85,114,66,8,10,1,1,10}, -{213,2,44,9,14,-3,1,11}, -{117,42,123,6,8,3,1,8}, -{181,2,140,6,11,3,1,8}, -{245,12,165,6,12,-1,1,8}, -{54,82,121,6,10,1,1,8}, -{86,76,18,10,10,1,-1,8}, -{246,72,173,6,11,0,1,8}, -{214,80,49,9,13,-2,1,11}, -{182,68,98,7,13,0,1,9}, -{118,15,47,9,8,3,-1,7}, -{55,72,129,6,10,1,1,8}, -{87,2,2,15,10,1,-1,13}, -{119,37,2,12,8,3,-1,10}, -{247,90,94,7,7,3,0,8}, -{215,2,93,7,7,3,0,8}, -{183,77,223,2,2,5,1,4}, -{56,62,129,6,10,1,1,8}, -{88,90,19,10,10,1,-1,8}, -{216,99,33,9,12,0,1,11}, -{248,2,128,6,8,3,1,8}, -{120,119,80,7,8,3,0,7}, -{184,116,212,3,3,11,0,3}, -{89,28,65,9,10,1,-1,7}, -{217,38,80,8,13,-2,1,10}, -{249,52,157,6,11,0,1,8}, -{121,112,33,9,11,3,-1,7}, -{57,12,122,6,10,1,1,8}, -{185,23,229,3,6,1,0,5}, -{218,26,79,8,13,-2,1,10}, -{250,42,149,6,11,0,1,8}, -{90,32,136,6,10,1,1,8}, -{122,112,119,6,8,3,1,7}, -{58,89,229,2,8,3,1,4}, -{186,37,205,4,5,1,0,5}, -{219,50,80,8,13,-2,1,10}, -{91,58,227,3,12,1,1,4}, -{123,103,180,5,12,1,0,5}, -{251,12,150,6,11,0,1,8}, -{59,71,234,2,9,3,1,4}, -{187,31,181,5,6,4,1,7}, -{188,16,33,10,10,1,0,10}, -{124,83,229,2,14,0,3,7}, -{220,62,79,8,13,-2,1,10}, -{252,92,133,6,11,0,1,8}, -{92,97,80,7,10,1,-1,5}, -{60,92,153,6,7,3,1,8}, -{189,47,20,11,10,1,0,10}, -{253,28,47,9,14,0,-1,7}, -{221,54,48,9,13,-2,-1,7}, -{93,44,226,3,12,1,0,4}, -{125,110,196,5,12,1,0,5}, -{61,79,112,7,5,4,0,8}, -{190,104,19,10,10,1,0,10}, -{222,32,165,6,10,1,1,8}, -{254,102,119,6,14,0,1,8}, -{62,112,158,6,7,3,1,8}, -{94,86,80,7,6,1,0,7}, -{126,62,158,6,3,5,1,8}, -{223,82,135,6,11,0,1,8}, -{255,41,48,9,14,0,-1,7}, -{191,94,180,5,11,3,0,6}, -{63,74,202,5,10,1,0,6}, -{95,92,148,6,1,12,0,6}, -}; -static const int _lodpi_font_kerning_pair_count=0; -static const int _lodpi_font_kerning_pairs[1][3]={ -{0,0,0}, -}; -static const int _lodpi_font_img_width=128; -static const int _lodpi_font_img_height=256; -static const int _lodpi_font_img_data_size=12909; -static const unsigned char _lodpi_font_img_data[12909]={ -137, -80, -78, -71, -13, -10, -26, -10, -0, -0, -0, -13, -73, -72, -68, -82, -0, -0, -0, -128, -0, -0, -1, -0, -8, -6, -0, -0, -0, -123, -249, -126, -167, -0, -0, -32, -0, -73, -68, -65, -84, -120, -156, -237, -157, -119, -184, -30, -85, -181, -255, -191, -59, -128, -8, -210, -155, -82, -77, -232, -221, -2, -210, -164, -132, -26, -154, -5, -164, -131, -63, -224, -98, -65, -4, -68, -46, -8, -42, -23, -84, -80, -41, -94, -16, -21, -17, -197, -10, -22, -80, -17, -21, -4, -5, -65, -16, -41, -42, -185, -116, -19, -106, -128, -80, -46, -32, -33, -148, -208, -18, -62, -191, -63, -214, -122, -115, -230, -204, -217, -51, -179, -103, -222, -121, -223, -115, -146, -123, -190, -207, -115, -158, -51, -101, -237, -50, -243, -174, -217, -123, -237, -213, -182, -52, -138, -81, -140, -98, -20, -2, -150, -3, -222, -0, -54, -207, -93, -255, -32, -134, -247, -231, -174, -111, -237, -244, -203, -38, -212, -61, -31, -48, -149, -2, -68, -232, -87, -242, -91, -107, -102, -174, -253, -28, -184, -58, -115, -190, -178, -211, -172, -88, -243, -57, -199, -84, -220, -7, -216, -17, -184, -21, -120, -21, -184, -23, -216, -9, -56, -192, -143, -103, -0, -55, -1, -235, -36, -180, -245, -117, -224, -122, -224, -244, -58, -125, -244, -178, -247, -0, -239, -4, -222, -13, -220, -85, -179, -236, -55, -253, -57, -190, -89, -69, -59, -251, -101, -132, -16, -30, -151, -116, -171, -164, -157, -114, -52, -59, -74, -186, -94, -210, -118, -185, -235, -59, -73, -250, -123, -8, -225, -137, -132, -62, -237, -39, -233, -113, -73, -11, -248, -249, -206, -126, -188, -64, -230, -218, -108, -132, -16, -30, -145, -116, -175, -164, -205, -252, -129, -230, -149, -180, -161, -164, -21, -129, -133, -156, -108, -115, -73, -147, -67, -8, -143, -150, -53, -12, -28, -9, -76, -3, -206, -4, -198, -74, -186, -49, -161, -191, -103, -75, -58, -76, -210, -242, -178, -103, -191, -88, -210, -17, -146, -118, -151, -52, -78, -210, -51, -146, -190, -157, -80, -207, -102, -33, -132, -45, -37, -109, -157, -64, -155, -199, -204, -204, -241, -172, -212, -66, -192, -119, -37, -45, -41, -105, -13, -73, -75, -251, -121, -114, -225, -19, -128, -137, -185, -107, -15, -1, -59, -3, -255, -202, -93, -191, -11, -248, -108, -98, -189, -119, -2, -251, -250, -49, -64, -158, -153, -98, -101, -206, -1, -206, -247, -227, -109, -128, -75, -129, -31, -117, -70, -34, -224, -188, -20, -14, -7, -110, -241, -209, -226, -76, -224, -57, -224, -136, -10, -122, -128, -79, -100, -206, -215, -205, -247, -217, -71, -136, -23, -18, -218, -190, -223, -203, -222, -93, -69, -59, -34, -0, -172, -159, -29, -214, -129, -53, -128, -71, -252, -120, -42, -176, -188, -31, -175, -232, -15, -182, -118, -66, -157, -59, -1, -143, -250, -87, -92, -135, -1, -62, -216, -121, -113, -192, -89, -192, -97, -192, -65, -192, -185, -126, -237, -110, -114, -211, -82, -65, -61, -165, -67, -126, -132, -30, -96, -219, -204, -249, -88, -191, -54, -54, -115, -109, -115, -24, -58, -117, -69, -234, -186, -12, -248, -53, -240, -251, -58, -125, -240, -178, -59, -121, -187, -249, -17, -185, -167, -101, -59, -95, -252, -193, -126, -124, -100, -230, -43, -252, -17, -112, -144, -31, -127, -28, -184, -47, -177, -190, -107, -128, -207, -100, -206, -135, -160, -160, -220, -162, -192, -107, -254, -255, -62, -76, -46, -88, -14, -120, -16, -88, -204, -239, -45, -146, -216, -135, -228, -23, -226, -116, -155, -103, -206, -59, -12, -176, -66, -230, -90, -37, -3, -0, -227, -128, -151, -252, -99, -121, -41, -203, -64, -137, -125, -238, -134, -121, -26, -151, -21, -112, -54, -112, -177, -31, -255, -1, -216, -211, -143, -247, -7, -46, -244, -227, -75, -129, -175, -37, -212, -245, -110, -224, -5, -96, -177, -204, -53, -252, -7, -121, -115, -231, -175, -164, -252, -77, -192, -129, -192, -29, -153, -107, -119, -0, -7, -3, -55, -212, -120, -166, -228, -23, -210, -34, -3, -156, -14, -252, -218, -143, -47, -1, -78, -171, -209, -223, -198, -204, -211, -45, -227, -9, -216, -22, -19, -154, -22, -0, -158, -7, -22, -247, -235, -111, -5, -30, -199, -36, -250, -231, -201, -173, -22, -10, -234, -250, -121, -236, -139, -39, -97, -10, -240, -242, -39, -3, -55, -0, -95, -201, -92, -59, -195, -25, -227, -164, -196, -58, -106, -189, -144, -54, -24, -192, -25, -251, -25, -96, -15, -63, -223, -195, -207, -11, -153, -61, -87, -190, -27, -230, -105, -92, -182, -83, -193, -188, -206, -0, -31, -7, -110, -206, -221, -187, -29, -27, -9, -158, -162, -122, -57, -53, -22, -27, -166, -215, -206, -126, -237, -53, -25, -96, -75, -167, -223, -44, -115, -109, -187, -252, -181, -138, -58, -106, -189, -144, -150, -24, -224, -64, -255, -72, -22, -240, -243, -55, -3, -211, -129, -3, -19, -250, -219, -152, -121, -186, -101, -188, -108, -69, -63, -195, -164, -231, -47, -230, -174, -127, -205, -191, -190, -31, -36, -212, -113, -54, -240, -219, -200, -117, -72, -156, -2, -186, -69, -107, -47, -164, -126, -187, -127, -39, -142, -91, -18, -202, -118, -195, -60, -141, -203, -230, -43, -218, -219, -59, -252, -222, -220, -245, -9, -126, -253, -3, -21, -229, -23, -7, -94, -4, -198, -71, -238, -13, -65, -173, -206, -213, -64, -107, -47, -164, -94, -155, -239, -1, -102, -97, -43, -168, -236, -200, -183, -154, -95, -223, -176, -162, -124, -55, -204, -211, -184, -236, -92, -137, -225, -120, -33, -216, -106, -233, -55, -5, -247, -46, -5, -126, -88, -82, -182, -49, -243, -116, -203, -120, -115, -29, -134, -227, -133, -0, -75, -2, -47, -3, -91, -21, -220, -223, -214, -239, -47, -89, -112, -191, -27, -230, -105, -92, -118, -174, -196, -156, -246, -66, -186, -97, -158, -110, -25, -111, -174, -195, -232, -11, -105, -1, -116, -163, -78, -180, -242, -75, -1, -255, -11, -252, -180, -102, -185, -159, -123, -185, -165, -154, -180, -59, -138, -150, -64, -55, -234, -68, -43, -127, -5, -166, -195, -175, -171, -139, -159, -7, -51, -163, -254, -161, -73, -187, -163, -104, -1, -116, -171, -78, -156, -139, -1, -44, -3, -188, -14, -108, -19, -185, -183, -131, -223, -91, -102, -56, -250, -214, -4, -69, -95, -231, -39, -36, -93, -233, -182, -246, -63, -250, -121, -50, -186, -153, -62, -186, -157, -122, -122, -141, -16, -194, -83, -146, -174, -146, -180, -103, -228, -246, -158, -146, -254, -228, -52, -131, -0, -44, -136, -57, -121, -124, -46, -114, -239, -72, -191, -183, -96, -228, -222, -217, -152, -129, -238, -62, -224, -204, -204, -245, -177, -152, -133, -54, -201, -32, -150, -12, -90, -208, -158, -117, -51, -125, -212, -41, -235, -194, -222, -143, -129, -215, -114, -215, -43, -95, -142, -51, -217, -171, -184, -173, -35, -119, -239, -77, -192, -211, -80, -104, -169, -220, -215, -229, -148, -121, -50, -215, -230, -245, -50, -251, -148, -180, -185, -30, -102, -28, -219, -58, -115, -109, -99, -204, -79, -97, -221, -130, -50, -107, -250, -180, -184, -46, -240, -104, -230, -250, -133, -192, -167, -138, -218, -106, -12, -186, -212, -158, -117, -51, -125, -212, -45, -11, -252, -2, -179, -77, -204, -200, -93, -175, -124, -57, -206, -0, -55, -147, -113, -254, -200, -220, -219, -3, -184, -177, -132, -1, -22, -244, -119, -180, -77, -230, -218, -118, -217, -247, -86, -210, -238, -199, -128, -39, -129, -101, -157, -129, -31, -6, -62, -86, -81, -230, -58, -103, -174, -143, -248, -249, -187, -48, -103, -147, -55, -149, -149, -107, -4, -186, -212, -158, -49, -12, -150, -44, -96, -102, -230, -56, -233, -229, -248, -51, -29, -79, -206, -224, -229, -247, -254, -0, -28, -81, -196, -0, -78, -243, -35, -220, -57, -197, -207, -207, -3, -126, -148, -216, -223, -95, -96, -190, -130, -127, -0, -126, -145, -88, -102, -89, -224, -223, -126, -252, -39, -220, -195, -170, -85, -208, -189, -30, -123, -88, -44, -89, -57, -6, -72, -122, -57, -206, -0, -219, -98, -243, -235, -26, -153, -235, -203, -3, -255, -196, -220, -208, -202, -24, -96, -59, -124, -26, -240, -191, -167, -200, -120, -18, -85, -180, -189, -48, -54, -69, -61, -69, -197, -28, -142, -59, -189, -2, -107, -97, -158, -85, -219, -99, -14, -171, -1, -27, -253, -94, -43, -43, -95, -11, -116, -169, -61, -99, -152, -44, -89, -29, -6, -200, -188, -156, -147, -128, -73, -216, -240, -26, -157, -10, -156, -1, -182, -195, -124, -14, -190, -156, -185, -254, -121, -224, -112, -191, -87, -198, -0, -99, -128, -199, -156, -81, -182, -246, -31, -52, -105, -201, -235, -31, -218, -116, -108, -154, -27, -178, -154, -200, -209, -222, -130, -9, -128, -119, -2, -239, -243, -231, -219, -14, -88, -221, -127, -175, -25, -101, -229, -147, -65, -11, -218, -51, -134, -201, -146, -5, -204, -244, -47, -162, -243, -114, -54, -197, -28, -90, -22, -3, -166, -23, -148, -233, -48, -192, -106, -206, -40, -99, -188, -142, -187, -49, -107, -102, -41, -3, -120, -29, -103, -96, -186, -142, -179, -73, -116, -253, -118, -198, -254, -23, -240, -73, -224, -88, -108, -4, -90, -168, -186, -164, -4, -236, -7, -252, -17, -152, -31, -184, -26, -120, -27, -240, -122, -74, -217, -158, -131, -97, -182, -100, -117, -94, -78, -238, -218, -4, -224, -138, -2, -122, -112, -199, -20, -76, -224, -219, -22, -24, -143, -207, -201, -137, -12, -240, -14, -108, -249, -54, -25, -88, -191, -170, -143, -94, -230, -76, -224, -42, -103, -182, -121, -128, -127, -0, -149, -46, -230, -216, -202, -228, -126, -44, -86, -224, -28, -6, -166, -202, -153, -85, -101, -251, -2, -134, -209, -146, -149, -125, -57, -153, -107, -155, -2, -215, -82, -108, -121, -203, -50, -192, -161, -216, -114, -242, -2, -96, -123, -191, -86, -201, -0, -78, -119, -59, -112, -123, -21, -157, -211, -110, -9, -60, -75, -38, -152, -5, -243, -196, -126, -149, -204, -210, -176, -160, -236, -81, -192, -5, -126, -252, -47, -108, -138, -155, -228, -207, -49, -188, -90, -83, -134, -217, -146, -149, -125, -57, -126, -190, -13, -112, -17, -176, -112, -73, -153, -44, -3, -44, -14, -252, -219, -95, -236, -24, -191, -150, -196, -0, -169, -0, -222, -2, -60, -64, -68, -166, -193, -228, -144, -7, -129, -183, -20, -148, -93, -4, -147, -55, -198, -70, -238, -141, -140, -17, -96, -184, -16, -123, -57, -206, -48, -247, -101, -190, -146, -141, -34, -229, -102, -51, -128, -159, -255, -146, -140, -11, -92, -219, -12, -48, -226, -1, -108, -229, -28, -122, -47, -176, -113, -230, -250, -242, -152, -144, -212, -174, -202, -113, -20, -35, -11, -152, -20, -190, -13, -166, -139, -191, -46, -115, -253, -7, -192, -145, -195, -217, -183, -81, -244, -1, -216, -154, -244, -45, -192, -66, -248, -210, -9, -211, -63, -223, -75, -47, -84, -142, -163, -24, -89, -192, -140, -20, -29, -6, -120, -222, -175, -93, -78, -137, -113, -195, -105, -26, -27, -100, -156, -110, -39, -204, -0, -116, -149, -255, -93, -6, -76, -232, -254, -137, -134, -31, -12, -198, -12, -44, -170, -233, -68, -42, -236, -5, -5, -117, -189, -189, -224, -250, -82, -190, -138, -200, -135, -245, -111, -130, -197, -101, -44, -157, -218, -192, -13, -153, -41, -224, -111, -216, -154, -248, -159, -152, -150, -107, -136, -108, -144, -41, -215, -141, -65, -230, -100, -224, -123, -89, -9, -31, -88, -218, -175, -149, -70, -254, -212, -101, -28, -111, -107, -50, -240, -138, -255, -159, -0, -124, -26, -51, -204, -60, -5, -156, -75, -196, -28, -155, -41, -191, -14, -240, -83, -23, -56, -159, -198, -150, -157, -167, -149, -49, -184, -255, -240, -157, -24, -136, -177, -192, -7, -48, -101, -213, -223, -139, -218, -194, -116, -254, -223, -243, -118, -94, -5, -166, -248, -52, -252, -34, -176, -92, -65, -153, -139, -240, -24, -206, -204, -181, -115, -113, -187, -74, -18, -128, -205, -48, -201, -249, -126, -96, -139, -140, -76, -16, -149, -13, -34, -229, -107, -25, -100, -128, -93, -24, -80, -186, -128, -169, -70, -31, -7, -142, -245, -107, -223, -43, -250, -65, -155, -48, -14, -102, -194, -125, -15, -182, -228, -251, -152, -127, -145, -147, -176, -105, -110, -37, -255, -0, -162, -241, -142, -192, -110, -254, -110, -246, -194, -109, -19, -152, -99, -200, -119, -252, -171, -142, -46, -55, -253, -185, -182, -203, -93, -91, -8, -91, -242, -157, -16, -161, -95, -10, -19, -184, -127, -131, -41, -153, -22, -1, -54, -192, -84, -228, -143, -20, -49, -27, -3, -150, -200, -5, -253, -124, -126, -44, -186, -107, -151, -24, -125, -37, -128, -125, -112, -229, -2, -17, -217, -160, -160, -76, -45, -131, -12, -230, -50, -182, -148, -31, -131, -169, -53, -87, -195, -194, -211, -23, -244, -31, -244, -178, -72, -185, -44, -227, -172, -4, -252, -197, -31, -246, -119, -126, -173, -144, -113, -50, -117, -204, -139, -105, -24, -63, -150, -185, -182, -7, -240, -112, -132, -118, -172, -191, -252, -149, -10, -234, -186, -144, -140, -45, -33, -119, -111, -8, -3, -248, -245, -207, -17, -201, -250, -1, -252, -183, -51, -229, -152, -28, -237, -3, -152, -138, -250, -11, -5, -237, -4, -103, -170, -255, -231, -231, -123, -249, -199, -52, -79, -140, -190, -20, -152, -86, -237, -94, -92, -181, -73, -68, -54, -40, -40, -151, -55, -200, -148, -90, -171, -24, -156, -238, -5, -224, -67, -192, -113, -152, -110, -188, -163, -140, -185, -42, -82, -46, -203, -56, -191, -195, -244, -241, -99, -112, -61, -64, -9, -227, -172, -128, -133, -188, -61, -236, -127, -49, -188, -17, -41, -247, -117, -224, -56, -63, -94, -13, -83, -27, -79, -197, -134, -230, -21, -48, -245, -245, -189, -145, -114, -67, -144, -185, -247, -65, -224, -229, -72, -153, -201, -157, -182, -252, -124, -93, -76, -167, -177, -165, -191, -155, -59, -242, -101, -50, -180, -159, -7, -174, -241, -227, -203, -129, -83, -139, -104, -75, -1, -124, -138, -140, -93, -27, -147, -7, -182, -198, -101, -131, -146, -114, -121, -131, -76, -169, -181, -42, -194, -0, -87, -96, -115, -241, -58, -153, -235, -49, -6, -200, -150, -123, -14, -88, -61, -66, -19, -43, -247, -103, -204, -211, -104, -17, -103, -24, -176, -16, -184, -172, -237, -97, -136, -233, -25, -251, -34, -215, -246, -227, -107, -128, -195, -252, -248, -85, -44, -98, -122, -12, -240, -74, -164, -92, -39, -16, -118, -167, -124, -221, -192, -238, -192, -139, -145, -50, -175, -0, -123, -249, -241, -188, -152, -12, -118, -150, -159, -239, -89, -244, -46, -253, -254, -114, -152, -208, -183, -14, -230, -151, -184, -70, -17, -109, -33, -252, -229, -60, -202, -96, -93, -245, -32, -217, -160, -162, -124, -178, -181, -202, -127, -240, -37, -253, -24, -167, -61, -16, -56, -207, -175, -45, -69, -252, -75, -206, -51, -192, -106, -17, -154, -24, -3, -188, -4, -236, -234, -199, -227, -188, -205, -83, -202, -158, -199, -105, -95, -102, -96, -110, -125, -9, -120, -59, -54, -26, -130, -141, -150, -139, -3, -79, -23, -148, -133, -248, -20, -240, -37, -224, -214, -200, -245, -87, -24, -200, -199, -112, -2, -54, -34, -116, -204, -227, -123, -17, -25, -53, -114, -229, -127, -135, -77, -191, -133, -31, -106, -207, -64, -77, -107, -21, -102, -219, -254, -185, -31, -119, -24, -32, -96, -158, -50, -155, -96, -115, -249, -16, -199, -208, -28, -227, -252, -22, -243, -34, -26, -195, -192, -180, -80, -196, -56, -147, -176, -233, -226, -173, -216, -84, -112, -163, -51, -208, -222, -206, -16, -239, -5, -118, -143, -148, -123, -9, -88, -212, -143, -31, -194, -134, -227, -29, -176, -17, -96, -25, -44, -140, -254, -226, -130, -103, -28, -194, -0, -94, -230, -73, -224, -63, -35, -244, -147, -49, -47, -165, -245, -48, -33, -117, -211, -204, -189, -227, -129, -59, -99, -237, -228, -222, -41, -184, -235, -88, -95, -65, -3, -107, -21, -240, -85, -44, -16, -36, -43, -205, -47, -133, -45, -45, -79, -46, -40, -147, -101, -156, -149, -176, -37, -224, -52, -60, -137, -85, -9, -227, -108, -143, -205, -253, -211, -129, -111, -97, -67, -236, -209, -126, -237, -117, -108, -78, -143, -249, -7, -78, -196, -163, -156, -177, -161, -123, -138, -63, -215, -4, -224, -127, -48, -225, -108, -229, -130, -190, -130, -77, -1, -11, -96, -242, -194, -1, -152, -176, -118, -13, -145, -21, -18, -230, -91, -112, -15, -54, -141, -158, -150, -185, -62, -143, -51, -71, -244, -157, -100, -232, -118, -114, -134, -45, -52, -130, -245, -4, -116, -97, -173, -242, -31, -244, -10, -108, -202, -184, -218, -143, -75, -151, -47, -77, -24, -167, -41, -128, -255, -44, -98, -224, -132, -178, -89, -60, -143, -45, -169, -143, -2, -230, -43, -160, -95, -6, -147, -222, -95, -198, -150, -172, -11, -99, -115, -250, -149, -206, -116, -67, -150, -129, -206, -28, -11, -97, -194, -232, -93, -192, -25, -77, -250, -58, -199, -161, -9, -227, -52, -108, -103, -62, -255, -98, -127, -69, -198, -117, -219, -127, -172, -86, -29, -51, -177, -21, -204, -115, -152, -101, -242, -49, -76, -168, -155, -234, -35, -86, -52, -84, -206, -71, -150, -87, -188, -220, -15, -128, -249, -219, -236, -147, -168, -25, -218, -53, -55, -194, -153, -224, -211, -152, -84, -62, -13, -243, -29, -120, -0, -23, -88, -91, -110, -43, -26, -31, -208, -55, -144, -203, -172, -73, -196, -109, -122, -20, -115, -15, -98, -95, -247, -254, -146, -54, -240, -227, -219, -36, -213, -138, -238, -29, -197, -28, -142, -58, -67, -190, -207, -89, -175, -145, -243, -184, -193, -194, -157, -94, -45, -154, -183, -114, -180, -141, -194, -200, -71, -209, -3, -48, -216, -18, -213, -17, -64, -190, -77, -137, -73, -17, -211, -174, -125, -35, -119, -237, -219, -20, -172, -141, -35, -229, -107, -133, -145, -211, -48, -139, -246, -40, -163, -85, -128, -1, -75, -212, -165, -12, -88, -162, -214, -243, -31, -248, -62, -92, -25, -18, -41, -183, -11, -166, -194, -237, -228, -2, -126, -147, -11, -73, -61, -177, -233, -99, -214, -183, -14, -3, -36, -121, -227, -122, -185, -36, -70, -195, -20, -66, -19, -49, -69, -204, -237, -84, -104, -63, -115, -101, -107, -51, -39, -53, -82, -187, -231, -202, -181, -203, -208, -152, -37, -234, -94, -114, -22, -36, -76, -203, -54, -153, -98, -139, -215, -60, -62, -98, -236, -226, -231, -187, -59, -35, -69, -95, -52, -109, -57, -48, -244, -8, -152, -54, -243, -84, -96, -9, -76, -115, -152, -148, -19, -217, -203, -214, -98, -78, -224, -187, -152, -86, -114, -117, -76, -135, -145, -156, -218, -189, -238, -200, -153, -82, -225, -32, -75, -84, -238, -222, -241, -101, -28, -141, -41, -102, -126, -230, -199, -191, -161, -192, -108, -153, -161, -175, -229, -192, -208, -132, -105, -176, -168, -160, -105, -228, -220, -176, -49, -83, -247, -179, -37, -35, -218, -11, -25, -102, -222, -139, -2, -61, -255, -92, -7, -50, -150, -168, -200, -189, -82, -67, -4, -102, -38, -157, -142, -185, -135, -205, -160, -192, -118, -158, -161, -175, -237, -192, -80, -151, -105, -252, -254, -137, -152, -118, -44, -100, -174, -221, -70, -196, -25, -35, -115, -255, -39, -152, -61, -98, -2, -166, -149, -59, -172, -236, -89, -188, -76, -19, -217, -169, -47, -101, -146, -209, -13, -3, -56, -205, -95, -49, -15, -153, -63, -150, -209, -57, -109, -109, -7, -134, -134, -76, -179, -136, -127, -237, -239, -243, -243, -157, -177, -136, -227, -178, -128, -145, -245, -48, -211, -246, -27, -192, -222, -9, -207, -82, -91, -118, -234, -87, -153, -90, -192, -116, -205, -199, -23, -220, -75, -177, -68, -29, -236, -47, -46, -150, -58, -37, -70, -95, -203, -129, -161, -9, -211, -100, -218, -185, -193, -143, -255, -74, -193, -52, -231, -247, -223, -235, -12, -115, -54, -166, -233, -251, -25, -3, -206, -41, -135, -19, -113, -200, -160, -129, -236, -212, -175, -50, -181, -128, -37, -130, -190, -47, -210, -64, -146, -37, -170, -65, -123, -181, -29, -24, -234, -50, -141, -211, -45, -236, -95, -253, -103, -49, -169, -57, -26, -126, -229, -180, -119, -0, -95, -242, -227, -149, -24, -112, -200, -92, -206, -153, -237, -195, -145, -50, -181, -101, -167, -126, -149, -169, -133, -204, -16, -115, -21, -230, -138, -148, -181, -68, -117, -63, -196, -196, -219, -172, -229, -192, -208, -132, -105, -188, -220, -113, -24, -134, -216, -224, -115, -116, -47, -2, -187, -101, -206, -87, -198, -28, -100, -94, -0, -190, -95, -80, -166, -246, -212, -217, -175, -50, -41, -200, -238, -26, -246, -140, -164, -77, -36, -77, -145, -244, -39, -73, -255, -150, -101, -8, -123, -80, -182, -251, -85, -161, -67, -104, -23, -248, -158, -164, -237, -37, -37, -165, -109, -245, -157, -205, -174, -148, -116, -150, -108, -199, -178, -201, -137, -237, -116, -148, -82, -67, -210, -215, -231, -112, -163, -164, -227, -176, -101, -220, -56, -73, -155, -202, -118, -239, -154, -79, -210, -180, -196, -182, -230, -40, -204, -155, -61, -241, -45, -224, -62, -218, -199, -246, -103, -74, -154, -33, -233, -162, -26, -101, -190, -39, -233, -119, -170, -215, -207, -212, -32, -207, -131, -36, -125, -93, -198, -248, -11, -73, -154, -36, -233, -11, -146, -110, -146, -116, -45, -240, -74, -8, -33, -191, -130, -152, -34, -41, -234, -16, -226, -215, -239, -143, -92, -239, -87, -153, -222, -131, -226, -165, -73, -52, -89, -34, -93, -58, -48, -208, -192, -235, -133, -129, -93, -63, -86, -173, -211, -86, -98, -221, -181, -101, -167, -126, -149, -233, -57, -40, -95, -154, -60, -64, -102, -179, -168, -76, -153, -218, -14, -12, -45, -48, -77, -47, -25, -160, -182, -236, -212, -175, -50, -61, -7, -189, -94, -154, -12, -212, -215, -91, -175, -151, -46, -145, -25, -5, -31, -167, -190, -82, -167, -167, -101, -154, -62, -80, -82, -198, -110, -122, -189, -52, -25, -197, -240, -128, -196, -140, -221, -244, -104, -105, -50, -138, -254, -97, -222, -216, -197, -16, -194, -44, -73, -71, -181, -80, -127, -123, -73, -12, -71, -209, -19, -12, -50, -37, -82, -223, -216, -48, -69, -229, -75, -147, -41, -249, -139, -52, -180, -210, -213, -133, -11, -125, -177, -168, -156, -194, -220, -63, -152, -47, -195, -49, -152, -63, -192, -75, -152, -98, -232, -110, -160, -141, -143, -97, -100, -163, -66, -162, -47, -146, -76, -171, -150, -38, -255, -85, -208, -86, -19, -43, -93, -45, -230, -172, -203, -0, -254, -227, -95, -135, -5, -101, -236, -224, -140, -250, -86, -224, -253, -177, -122, -114, -253, -122, -202, -143, -151, -6, -30, -47, -233, -79, -7, -149, -201, -34, -98, -253, -199, -98, -45, -175, -35, -30, -195, -56, -147, -248, -30, -6, -105, -201, -174, -104, -102, -160, -40, -91, -154, -220, -73, -65, -6, -76, -106, -90, -233, -26, -50, -103, -93, -6, -56, -14, -75, -252, -80, -107, -4, -2, -118, -197, -147, -81, -98, -219, -202, -71, -211, -220, -123, -127, -146, -147, -69, -228, -251, -15, -124, -25, -27, -153, -138, -114, -4, -212, -122, -222, -14, -178, -83, -192, -174, -146, -190, -239, -243, -255, -108, -132, -16, -222, -144, -169, -106, -135, -108, -22, -25, -81, -31, -79, -147, -116, -151, -164, -151, -36, -109, -17, -66, -24, -18, -1, -235, -229, -158, -151, -244, -223, -146, -58, -43, -136, -207, -74, -58, -35, -132, -240, -66, -65, -63, -63, -43, -233, -85, -73, -31, -10, -33, -220, -30, -66, -120, -62, -132, -112, -167, -108, -131, -134, -55, -36, -125, -166, -160, -92, -29, -236, -39, -233, -188, -6, -42, -239, -13, -36, -77, -140, -28, -199, -240, -122, -8, -225, -149, -16, -194, -148, -16, -194, -111, -37, -109, -37, -105, -41, -73, -71, -151, -53, -0, -28, -42, -105, -15, -73, -59, -250, -187, -107, -31, -180, -36, -209, -99, -86, -186, -137, -249, -145, -36, -66, -87, -199, -74, -215, -196, -122, -86, -138, -8, -253, -12, -96, -191, -148, -103, -116, -250, -231, -252, -111, -38, -102, -44, -26, -116, -92, -208, -159, -58, -201, -34, -240, -175, -247, -3, -62, -250, -69, -115, -4, -37, -212, -159, -60, -2, -180, -133, -79, -75, -90, -69, -210, -37, -148, -36, -67, -242, -175, -253, -12, -73, -95, -145, -116, -122, -8, -225, -165, -146, -58, -223, -46, -233, -161, -130, -123, -15, -122, -123, -49, -236, -44, -105, -129, -220, -223, -206, -69, -93, -146, -217, -38, -102, -163, -140, -97, -66, -8, -139, -133, -16, -22, -147, -244, -164, -164, -181, -252, -248, -127, -37, -173, -238, -199, -169, -184, -167, -164, -255, -235, -75, -250, -153, -108, -116, -28, -146, -189, -164, -13, -100, -25, -96, -138, -90, -48, -54, -132, -16, -238, -149, -89, -248, -222, -144, -84, -149, -201, -58, -213, -74, -215, -20, -157, -33, -119, -246, -95, -73, -159, -30, -148, -148, -207, -53, -80, -198, -48, -194, -118, -20, -39, -132, -48, -21, -75, -224, -52, -203, -13, -106, -117, -48, -70, -210, -172, -130, -123, -235, -203, -166, -166, -147, -128, -229, -107, -214, -155, -220, -120, -7, -151, -73, -58, -36, -63, -116, -251, -249, -193, -146, -46, -77, -173, -52, -132, -240, -247, -16, -194, -110, -33, -132, -170, -60, -182, -169, -86, -186, -41, -234, -189, -37, -236, -18, -73, -135, -146, -145, -176, -203, -24, -198, -135, -249, -127, -73, -90, -214, -143, -39, -75, -90, -174, -51, -53, -212, -104, -247, -157, -94, -54, -134, -11, -93, -86, -184, -72, -210, -79, -200, -172, -154, -34, -152, -37, -51, -91, -231, -241, -38, -229, -70, -182, -44, -178, -12, -112, -170, -19, -95, -73, -70, -162, -151, -116, -185, -211, -69, -51, -104, -245, -9, -173, -49, -103, -9, -206, -144, -9, -175, -127, -194, -178, -164, -45, -138, -173, -74, -162, -95, -158, -15, -243, -231, -72, -58, -209, -143, -207, -148, -116, -82, -102, -106, -168, -4, -102, -49, -253, -152, -108, -152, -47, -195, -103, -36, -173, -36, -233, -216, -18, -154, -123, -37, -189, -59, -114, -125, -35, -191, -151, -212, -161, -254, -24, -27, -6, -218, -75, -178, -210, -209, -204, -122, -86, -91, -40, -194, -188, -154, -191, -137, -101, -5, -155, -233, -130, -225, -189, -20, -236, -235, -227, -237, -79, -240, -227, -203, -40, -217, -234, -206, -251, -83, -39, -89, -196, -160, -254, -99, -233, -239, -95, -6, -54, -200, -211, -250, -253, -61, -48, -167, -217, -3, -253, -119, -124, -27, -112, -144, -11, -165, -251, -23, -245, -107, -142, -65, -191, -153, -51, -177, -79, -79, -116, -218, -247, -227, -183, -149, -208, -102, -145, -146, -44, -98, -8, -3, -99, -241, -23, -147, -41, -78, -43, -255, -62, -44, -132, -253, -101, -103, -222, -155, -129, -15, -117, -243, -140, -163, -24, -197, -40, -70, -49, -138, -185, -31, -62, -231, -220, -157, -159, -147, -42, -132, -166, -43, -124, -94, -126, -210, -231, -230, -239, -2, -151, -39, -180, -53, -98, -66, -181, -105, -24, -110, -62, -183, -32, -175, -9, -92, -84, -210, -49, -53, -202, -63, -41, -105, -89, -73, -107, -75, -90, -75, -210, -91, -252, -90, -21, -46, -144, -45, -125, -134, -4, -90, -228, -65, -162, -119, -82, -174, -76, -157, -144, -235, -236, -26, -185, -72, -33, -83, -214, -86, -85, -184, -249, -10, -152, -89, -121, -205, -18, -154, -206, -190, -73, -235, -213, -108, -187, -52, -112, -21, -203, -99, -124, -117, -238, -218, -159, -201, -236, -175, -148, -189, -1, -240, -31, -46, -161, -142, -203, -92, -47, -27, -1, -30, -3, -86, -201, -156, -47, -82, -213, -169, -186, -32, -209, -59, -41, -67, -223, -56, -228, -58, -177, -254, -90, -57, -148, -128, -243, -49, -251, -72, -105, -194, -12, -108, -207, -133, -115, -203, -104, -156, -110, -33, -6, -194, -213, -254, -234, -255, -3, -17, -203, -171, -255, -30, -15, -2, -255, -225, -231, -135, -96, -206, -186, -67, -173, -174, -206, -0, -91, -250, -131, -93, -158, -185, -94, -198, -0, -79, -144, -73, -146, -136, -173, -207, -163, -241, -244, -116, -107, -175, -110, -25, -77, -251, -131, -237, -228, -185, -178, -191, -167, -231, -128, -35, -74, -104, -215, -240, -23, -62, -63, -102, -30, -127, -103, -9, -237, -70, -216, -154, -189, -106, -131, -141, -19, -48, -179, -245, -79, -49, -31, -138, -159, -98, -9, -58, -190, -88, -64, -191, -137, -223, -127, -167, -255, -31, -178, -231, -67, -135, -16, -108, -139, -182, -133, -176, -112, -168, -78, -170, -215, -50, -6, -248, -1, -150, -174, -117, -49, -255, -241, -127, -82, -244, -197, -121, -253, -117, -236, -243, -181, -126, -160, -6, -244, -181, -250, -147, -185, -95, -39, -135, -210, -175, -128, -143, -250, -241, -190, -68, -82, -216, -230, -232, -255, -9, -28, -94, -65, -179, -8, -182, -165, -252, -49, -216, -212, -114, -180, -159, -23, -250, -49, -96, -142, -39, -51, -136, -56, -232, -228, -31, -38, -184, -13, -255, -40, -73, -103, -71, -135, -138, -193, -120, -171, -164, -103, -100, -170, -198, -251, -100, -115, -104, -105, -110, -128, -26, -152, -39, -210, -191, -54, -233, -107, -193, -25, -230, -110, -111, -39, -123, -189, -136, -193, -54, -148, -244, -46, -13, -132, -189, -93, -36, -105, -53, -96, -147, -146, -102, -206, -145, -52, -36, -93, -109, -22, -238, -15, -48, -77, -246, -27, -29, -44, -179, -190, -78, -171, -240, -99, -24, -39, -233, -89, -255, -63, -8, -209, -23, -22, -66, -248, -181, -44, -69, -220, -41, -42, -49, -36, -132, -16, -118, -9, -33, -28, -18, -66, -88, -38, -132, -240, -182, -16, -194, -193, -33, -132, -29, -203, -30, -96, -14, -71, -29, -33, -249, -84, -73, -167, -116, -12, -98, -238, -88, -243, -21, -73, -101, -177, -18, -191, -144, -244, -54, -42, -118, -17, -149, -180, -177, -164, -137, -33, -132, -95, -74, -186, -69, -210, -123, -139, -8, -49, -175, -171, -237, -36, -189, -71, -210, -14, -120, -198, -244, -24, -225, -160, -33, -17, -203, -160, -253, -60, -182, -189, -74, -215, -115, -116, -131, -41, -96, -36, -210, -215, -18, -146, -155, -0, -115, -205, -251, -101, -75, -117, -45, -137, -201, -105, -157, -233, -124, -79, -63, -95, -162, -67, -83, -56, -100, -134, -16, -30, -146, -244, -85, -73, -95, -106, -163, -51, -115, -9, -238, -151, -116, -190, -164, -111, -245, -176, -141, -115, -37, -189, -159, -130, -13, -162, -106, -226, -219, -146, -238, -8, -33, -252, -74, -146, -124, -196, -184, -211, -175, -75, -170, -158, -51, -191, -38, -11, -19, -111, -3, -141, -236, -213, -61, -68, -147, -254, -204, -39, -233, -68, -73, -235, -119, -190, -170, -182, -17, -66, -184, -95, -210, -181, -50, -51, -113, -183, -117, -237, -29, -66, -152, -144, -187, -182, -67, -8, -97, -246, -86, -128, -217, -252, -0, -33, -132, -112, -117, -142, -248, -245, -16, -194, -58, -33, -132, -50, -71, -132, -84, -212, -181, -87, -215, -253, -129, -234, -210, -55, -177, -159, -215, -21, -146, -155, -226, -28, -73, -31, -197, -115, -47, -206, -21, -160, -166, -189, -26, -83, -209, -126, -62, -114, -253, -11, -46, -141, -119, -75, -95, -183, -63, -121, -25, -233, -114, -44, -151, -208, -248, -54, -101, -128, -185, -26, -212, -176, -87, -55, -248, -129, -106, -59, -68, -212, -236, -79, -158, -1, -90, -21, -146, -71, -17, -65, -157, -31, -168, -9, -125, -205, -190, -12, -89, -53, -96, -46, -237, -79, -14, -39, -3, -120, -191, -54, -175, -166, -76, -171, -108, -173, -252, -57, -17, -71, -68, -74, -208, -74, -71, -70, -32, -10, -24, -96, -62, -204, -130, -58, -231, -51, -0, -176, -10, -166, -99, -222, -193, -207, -183, -33, -183, -143, -95, -134, -182, -179, -207, -222, -26, -222, -129, -181, -41, -216, -119, -111, -20, -67, -129, -109, -170, -149, -36, -88, -99, -6, -173, -168, -219, -152, -223, -111, -117, -4, -216, -210, -153, -224, -243, -254, -127, -124, -5, -253, -88, -239, -64, -235, -169, -87, -230, -102, -164, -254, -248, -78, -75, -217, -135, -149, -101, -0, -204, -225, -244, -38, -114, -41, -117, -235, -118, -238, -24, -175, -52, -154, -53, -52, -71, -155, -196, -0, -88, -46, -225, -171, -49, -227, -197, -19, -152, -141, -255, -255, -252, -94, -68, -41, -72, -101, -0, -44, -136, -247, -55, -192, -239, -105, -178, -103, -176, -87, -182, -149, -127, -249, -157, -72, -217, -237, -43, -232, -83, -25, -224, -70, -44, -217, -243, -50, -152, -229, -234, -160, -146, -135, -129, -129, -125, -252, -206, -1, -22, -79, -232, -119, -233, -48, -233, -52, -75, -97, -142, -34, -83, -177, -56, -200, -135, -40, -216, -45, -188, -23, -192, -220, -192, -99, -136, -109, -33, -123, -97, -1, -237, -175, -34, -180, -96, -91, -204, -125, -195, -191, -254, -232, -182, -244, -41, -29, -28, -135, -205, -249, -219, -248, -249, -22, -126, -190, -86, -73, -153, -84, -6, -120, -153, -34, -35, -196, -96, -58, -48, -223, -249, -133, -49, -251, -248, -157, -120, -26, -250, -132, -114, -101, -95, -201, -34, -152, -59, -245, -213, -152, -235, -215, -226, -254, -124, -177, -212, -175, -11, -3, -23, -224, -210, -125, -22, -17, -218, -107, -129, -255, -206, -93, -59, -25, -207, -77, -156, -187, -62, -134, -220, -62, -197, -254, -55, -36, -225, -21, -38, -92, -118, -238, -131, -5, -169, -188, -153, -8, -147, -251, -253, -142, -7, -212, -16, -121, -173, -22, -200, -24, -57, -252, -188, -40, -104, -177, -115, -63, -149, -1, -238, -33, -205, -219, -5, -6, -175, -181, -247, -7, -42, -85, -209, -9, -12, -112, -10, -230, -189, -84, -24, -172, -154, -161, -61, -39, -247, -187, -79, -194, -116, -10, -147, -34, -180, -91, -96, -211, -90, -103, -43, -219, -249, -253, -163, -121, -127, -69, -27, -109, -203, -0, -191, -194, -188, -160, -174, -168, -83, -119, -215, -168, -193, -0, -59, -99, -27, -62, -92, -71, -137, -61, -60, -194, -0, -31, -6, -162, -193, -150, -212, -27, -38, -239, -166, -98, -19, -139, -12, -237, -99, -185, -250, -238, -242, -235, -81, -135, -81, -44, -215, -241, -201, -126, -124, -176, -51, -123, -233, -143, -64, -15, -86, -1, -216, -200, -245, -16, -240, -233, -148, -122, -91, -65, -42, -3, -56, -237, -234, -152, -144, -50, -139, -200, -208, -235, -52, -48, -48, -5, -108, -140, -165, -145, -249, -66, -1, -109, -157, -97, -242, -21, -96, -159, -88, -61, -17, -218, -153, -53, -25, -96, -19, -204, -71, -112, -17, -108, -47, -225, -131, -83, -218, -105, -11, -29, -6, -240, -227, -205, -49, -5, -216, -187, -250, -213, -120, -237, -101, -32, -182, -15, -79, -52, -214, -63, -242, -53, -31, -153, -88, -39, -148, -15, -147, -175, -144, -176, -249, -131, -211, -78, -175, -195, -0, -126, -239, -114, -76, -250, -158, -74, -36, -206, -175, -151, -200, -50, -128, -159, -127, -5, -155, -182, -10, -19, -110, -12, -43, -176, -164, -75, -209, -52, -39, -254, -48, -219, -249, -241, -223, -129, -66, -239, -155, -220, -8, -112, -169, -143, -26, -69, -35, -192, -36, -224, -115, -137, -253, -251, -83, -42, -3, -48, -48, -250, -228, -177, -66, -74, -91, -35, -10, -184, -85, -43, -134, -46, -235, -93, -10, -51, -208, -44, -141, -249, -200, -95, -6, -252, -174, -128, -54, -203, -0, -187, -97, -75, -209, -162, -68, -83, -117, -100, -128, -51, -177, -249, -177, -242, -235, -196, -146, -80, -61, -156, -194, -0, -126, -61, -59, -13, -237, -196, -156, -170, -13, -37, -190, -76, -89, -180, -5, -6, -88, -28, -219, -170, -165, -147, -71, -231, -18, -10, -162, -104, -115, -12, -16, -48, -225, -45, -233, -203, -173, -232, -195, -210, -152, -167, -243, -223, -176, -53, -243, -34, -216, -178, -183, -72, -22, -121, -51, -176, -1, -176, -97, -25, -3, -208, -197, -8, -224, -12, -254, -40, -17, -165, -77, -65, -157, -253, -183, -55, -116, -30, -176, -143, -237, -205, -102, -0, -63, -255, -48, -45, -37, -143, -4, -150, -7, -190, -143, -133, -176, -205, -194, -76, -185, -215, -38, -148, -235, -201, -8, -128, -229, -58, -40, -202, -163, -56, -187, -174, -236, -95, -74, -189, -173, -2, -155, -103, -135, -36, -71, -192, -180, -121, -127, -241, -151, -217, -193, -227, -100, -156, -13, -71, -50, -128, -139, -129, -75, -18, -105, -43, -133, -64, -191, -63, -136, -121, -43, -104, -87, -199, -150, -197, -149, -35, -97, -66, -155, -187, -96, -59, -156, -206, -192, -130, -86, -214, -79, -233, -67, -87, -192, -134, -244, -147, -252, -248, -68, -224, -250, -158, -55, -218, -34, -176, -41, -33, -41, -223, -94, -42, -3, -212, -108, -255, -44, -160, -112, -151, -148, -154, -12, -48, -25, -155, -170, -150, -193, -132, -225, -222, -255, -22, -206, -109, -219, -250, -241, -182, -68, -244, -217, -125, -232, -67, -22, -47, -99, -243, -233, -197, -84, -107, -47, -255, -138, -173, -48, -62, -153, -216, -78, -171, -12, -128, -89, -236, -166, -1, -91, -149, -208, -68, -81, -64, -247, -137, -204, -249, -142, -64, -81, -178, -205, -40, -154, -90, -228, -238, -151, -244, -65, -31, -242, -63, -168, -226, -44, -87, -131, -128, -205, -233, -80, -18, -35, -87, -19, -157, -60, -128, -111, -147, -101, -50, -93, -82, -210, -144, -85, -64, -22, -33, -132, -45, -66, -8, -27, -133, -16, -206, -105, -169, -15, -117, -177, -159, -164, -199, -66, -8, -215, -85, -208, -197, -114, -28, -198, -144, -117, -96, -125, -81, -182, -215, -81, -111, -225, -67, -206, -52, -204, -106, -55, -145, -4, -205, -19, -176, -32, -166, -217, -123, -190, -136, -1, -48, -189, -122, -244, -47, -66, -59, -100, -152, -196, -36, -235, -178, -132, -147, -195, -14, -204, -101, -173, -116, -244, -169, -57, -5, -100, -149, -64, -155, -199, -70, -138, -50, -148, -186, -29, -3, -99, -60, -164, -105, -16, -66, -8, -255, -196, -214, -212, -107, -132, -16, -30, -76, -108, -235, -51, -178, -221, -184, -10, -99, -223, -66, -8, -141, -184, -23, -83, -254, -140, -149, -237, -36, -86, -103, -7, -178, -174, -128, -57, -204, -68, -87, -18, -49, -87, -122, -44, -50, -119, -13, -73, -63, -233, -109, -207, -26, -130, -26, -177, -239, -88, -122, -179, -14, -158, -164, -68, -213, -138, -45, -193, -158, -192, -148, -66, -111, -180, -49, -5, -20, -76, -147, -23, -208, -212, -25, -162, -89, -31, -138, -76, -188, -209, -37, -27, -233, -57, -0, -250, -54, -2, -228, -101, -128, -253, -101, -25, -175, -37, -11, -14, -141, -166, -112, -1, -206, -145, -101, -239, -92, -213, -255, -206, -85, -121, -34, -201, -175, -74, -58, -199, -179, -139, -183, -105, -174, -236, -204, -147, -11, -75, -26, -47, -11, -234, -248, -122, -164, -191, -27, -98, -186, -250, -233, -88, -130, -234, -139, -40, -73, -233, -150, -138, -16, -194, -27, -249, -84, -180, -153, -148, -180, -249, -62, -44, -41, -105, -47, -101, -194, -178, -70, -28, -72, -116, -211, -242, -121, -121, -163, -204, -249, -38, -20, -164, -71, -197, -52, -111, -143, -227, -198, -9, -231, -218, -182, -70, -128, -188, -12, -176, -63, -240, -108, -132, -246, -66, -204, -81, -99, -101, -108, -13, -126, -45, -5, -121, -253, -157, -62, -201, -123, -136, -30, -169, -207, -135, -5, -222, -239, -36, -165, -2, -102, -207, -62, -210, -143, -23, -244, -23, -28, -141, -104, -5, -110, -192, -44, -84, -171, -250, -31, -152, -255, -126, -87, -201, -143, -11, -24, -224, -96, -224, -177, -132, -178, -239, -163, -216, -32, -85, -199, -123, -168, -214, -20, -48, -162, -225, -47, -52, -73, -169, -224, -95, -252, -173, -152, -243, -196, -67, -88, -166, -176, -168, -22, -176, -232, -11, -1, -254, -210, -66, -127, -59, -234, -210, -69, -49, -143, -230, -251, -129, -51, -19, -202, -126, -130, -72, -184, -152, -223, -75, -246, -30, -154, -171, -224, -47, -180, -43, -165, -66, -141, -118, -218, -22, -2, -103, -98, -185, -120, -78, -166, -98, -83, -73, -44, -5, -206, -125, -192, -199, -11, -238, -39, -123, -15, -205, -85, -240, -23, -185, -109, -230, -188, -182, -68, -89, -163, -157, -182, -20, -65, -117, -219, -158, -199, -71, -182, -223, -80, -224, -146, -69, -13, -239, -161, -134, -125, -56, -44, -50, -26, -198, -18, -85, -64, -66, -192, -7, -240, -59, -76, -86, -153, -138, -9, -184, -93, -233, -1, -94, -173, -83, -184, -9, -90, -10, -53, -175, -13, -76, -192, -253, -129, -164, -197, -101, -123, -239, -148, -189, -168, -94, -10, -113, -75, -73, -186, -66, -82, -214, -219, -169, -82, -110, -41, -66, -8, -97, -182, -3, -42, -240, -99, -21, -107, -12, -163, -152, -123, -226, -207, -75, -224, -95, -251, -119, -100, -75, -214, -9, -33, -132, -178, -253, -143, -166, -168, -120, -11, -151, -170, -118, -86, -150, -229, -1, -218, -82, -166, -150, -30, -31, -66, -184, -41, -71, -182, -148, -164, -7, -61, -17, -68, -107, -0, -54, -147, -244, -33, -89, -194, -206, -100, -212, -182, -5, -96, -82, -242, -143, -128, -127, -99, -10, -160, -202, -8, -162, -17, -128, -111, -200, -244, -4, -71, -200, -146, -48, -117, -86, -36, -177, -156, -124, -127, -144, -37, -103, -168, -229, -219, -135, -101, -50, -189, -78, -54, -122, -76, -144, -237, -115, -116, -103, -132, -116, -41, -217, -222, -66, -41, -216, -4, -19, -204, -103, -96, -249, -0, -163, -242, -141, -143, -110, -223, -146, -244, -213, -16, -194, -163, -117, -250, -157, -173, -100, -208, -156, -83, -36, -3, -0, -223, -193, -44, -106, -171, -57, -205, -35, -192, -1, -141, -26, -237, -65, -255, -74, -202, -198, -240, -145, -8, -109, -45, -239, -161, -76, -185, -255, -194, -54, -131, -172, -218, -45, -237, -50, -204, -59, -234, -53, -76, -63, -242, -109, -34, -153, -70, -188, -127, -183, -97, -190, -23, -107, -98, -130, -235, -23, -10, -234, -60, -212, -239, -247, -126, -87, -117, -239, -244, -142, -153, -243, -143, -18, -89, -46, -22, -9, -55, -5, -117, -158, -128, -229, -3, -126, -26, -56, -151, -226, -141, -20, -187, -82, -123, -166, -130, -6, -222, -67, -88, -46, -130, -74, -247, -53, -44, -115, -215, -170, -216, -82, -123, -107, -204, -97, -245, -194, -8, -29, -120, -138, -87, -63, -255, -52, -241, -76, -39, -75, -98, -163, -241, -46, -169, -207, -215, -21, -48, -117, -234, -123, -51, -231, -59, -0, -67, -146, -20, -166, -50, -0, -176, -31, -166, -88, -90, -23, -88, -9, -83, -28, -197, -180, -110, -125, -99, -128, -38, -192, -252, -29, -191, -136, -133, -179, -189, -128, -69, -233, -84, -42, -187, -176, -61, -25, -103, -68, -174, -67, -38, -95, -32, -166, -188, -26, -34, -164, -99, -35, -114, -105, -6, -210, -50, -52, -241, -7, -184, -94, -210, -17, -62, -52, -190, -93, -182, -251, -103, -179, -128, -68, -195, -17, -146, -190, -22, -66, -184, -43, -132, -240, -136, -76, -151, -191, -103, -23, -245, -13, -23, -22, -146, -180, -181, -164, -221, -36, -173, -40, -233, -101, -153, -141, -164, -10, -243, -73, -122, -170, -224, -222, -152, -220, -113, -140, -225, -63, -42, -105, -51, -6, -150, -130, -83, -233, -165, -91, -186, -127, -165, -87, -98, -246, -128, -59, -157, -235, -159, -137, -208, -165, -142, -0, -207, -50, -20, -67, -76, -208, -145, -17, -96, -251, -17, -54, -2, -188, -64, -38, -0, -22, -83, -31, -199, -70, -198, -227, -125, -180, -91, -26, -139, -198, -190, -15, -56, -37, -66, -7, -158, -103, -216, -207, -143, -161, -7, -251, -25, -204, -230, -48, -76, -175, -125, -134, -255, -32, -79, -99, -33, -221, -135, -228, -11, -132, -16, -30, -9, -33, -236, -24, -66, -88, -40, -132, -176, -158, -164, -87, -148, -186, -45, -89, -113, -31, -246, -209, -96, -207, -151, -216, -136, -242, -146, -6, -123, -187, -172, -17, -171, -44, -195, -68, -179, -48, -19, -244, -55, -72, -8, -49, -111, -1, -119, -201, -190, -252, -14, -22, -146, -121, -232, -228, -241, -30, -217, -40, -58, -85, -210, -121, -146, -126, -36, -41, -154, -233, -91, -210, -145, -152, -0, -184, -150, -164, -195, -212, -75, -63, -2, -224, -63, -157, -27, -215, -198, -50, -91, -19, -99, -128, -72, -185, -137, -192, -137, -145, -235, -169, -35, -192, -45, -120, -112, -101, -5, -221, -13, -152, -6, -111, -25, -76, -58, -159, -28, -27, -1, -188, -221, -78, -124, -225, -38, -152, -106, -247, -231, -17, -186, -113, -88, -166, -243, -206, -30, -192, -151, -18, -217, -159, -55, -50, -58, -81, -208, -238, -129, -152, -13, -97, -75, -108, -133, -116, -3, -240, -157, -170, -231, -42, -121, -94, -128, -207, -96, -246, -141, -151, -177, -125, -7, -122, -23, -118, -134, -5, -54, -30, -229, -199, -215, -96, -214, -176, -91, -34, -116, -243, -99, -238, -226, -75, -98, -142, -35, -83, -137, -108, -221, -86, -131, -1, -246, -194, -156, -80, -62, -4, -172, -136, -229, -5, -216, -45, -66, -247, -78, -103, -182, -87, -48, -139, -229, -174, -37, -12, -144, -141, -45, -56, -0, -152, -22, -161, -187, -25, -11, -169, -30, -135, -229, -71, -250, -5, -112, -107, -132, -110, -213, -220, -223, -215, -40, -112, -148, -193, -150, -130, -79, -97, -82, -249, -15, -169, -200, -253, -95, -6, -127, -142, -118, -114, -255, -36, -54, -56, -29, -147, -52, -183, -193, -252, -214, -118, -45, -120, -113, -27, -97, -107, -216, -255, -197, -162, -124, -242, -251, -237, -118, -232, -146, -24, -192, -105, -63, -130, -133, -99, -205, -116, -134, -250, -84, -23, -207, -145, -103, -128, -168, -137, -24, -155, -179, -179, -50, -197, -150, -84, -24, -191, -48, -91, -194, -20, -122, -104, -43, -200, -180, -149, -204, -0, -152, -60, -52, -209, -71, -138, -169, -36, -140, -168, -177, -74, -166, -97, -67, -231, -141, -152, -37, -240, -3, -68, -156, -43, -106, -212, -7, -67, -163, -91, -74, -211, -184, -180, -129, -76, -187, -139, -49, -32, -100, -157, -29, -161, -251, -33, -240, -107, -204, -58, -184, -16, -230, -49, -84, -234, -41, -12, -236, -141, -41, -137, -122, -174, -66, -175, -201, -0, -79, -98, -250, -152, -69, -177, -216, -198, -241, -77, -26, -252, -7, -22, -230, -220, -217, -135, -230, -104, -224, -239, -181, -43, -26, -168, -47, -134, -82, -151, -237, -54, -144, -105, -107, -38, -230, -171, -112, -10, -241, -173, -89, -231, -199, -194, -179, -158, -197, -134, -236, -243, -168, -222, -0, -234, -22, -224, -179, -45, -244, -177, -213, -157, -202, -188, -190, -243, -233, -38, -132, -14, -248, -148, -191, -184, -253, -49, -65, -240, -1, -224, -208, -110, -59, -215, -111, -248, -51, -164, -200, -30, -187, -251, -151, -179, -157, -63, -251, -179, -148, -7, -107, -116, -146, -47, -44, -217, -66, -31, -239, -200, -48, -192, -237, -45, -212, -55, -30, -115, -209, -159, -142, -109, -47, -91, -223, -153, -5, -203, -171, -251, -15, -127, -17, -211, -128, -47, -85, -125, -17, -253, -4, -22, -45, -124, -154, -247, -237, -25, -34, -137, -161, -157, -46, -149, -1, -38, -147, -209, -239, -99, -94, -66, -49, -227, -77, -231, -254, -111, -40, -217, -129, -12, -203, -104, -50, -209, -153, -228, -206, -70, -195, -112, -67, -120, -123, -7, -249, -135, -123, -11, -240, -219, -126, -53, -12, -53, -247, -186, -43, -168, -99, -8, -34, -116, -7, -99, -235, -250, -119, -97, -110, -107, -79, -1, -187, -23, -212, -151, -194, -0, -47, -98, -38, -212, -206, -249, -86, -68, -20, -55, -126, -111, -21, -159, -82, -10, -179, -112, -249, -87, -253, -77, -204, -135, -240, -43, -64, -212, -220, -235, -253, -107, -85, -165, -141, -9, -180, -59, -249, -241, -102, -244, -58, -56, -6, -56, -28, -19, -174, -240, -161, -39, -105, -79, -191, -130, -186, -242, -14, -149, -151, -17, -9, -156, -196, -150, -166, -95, -204, -156, -159, -76, -68, -7, -94, -131, -1, -174, -196, -214, -254, -227, -176, -117, -251, -31, -129, -95, -23, -208, -126, -11, -184, -170, -162, -190, -89, -157, -175, -30, -19, -66, -135, -232, -247, -51, -253, -171, -197, -0, -20, -120, -92, -103, -238, -159, -238, -31, -196, -193, -192, -73, -192, -53, -101, -244, -93, -193, -27, -121, -209, -135, -156, -21, -176, -124, -193, -155, -69, -232, -222, -135, -101, -5, -123, -17, -27, -182, -47, -7, -86, -175, -168, -123, -107, -108, -41, -51, -54, -114, -111, -42, -153, -204, -223, -152, -222, -160, -177, -39, -141, -247, -253, -82, -6, -166, -148, -11, -137, -236, -78, -234, -95, -244, -139, -84, -88, -219, -178, -63, -44, -230, -36, -251, -199, -42, -58, -63, -239, -154, -1, -156, -102, -87, -76, -249, -4, -137, -249, -144, -26, -1, -83, -194, -156, -154, -64, -247, -117, -224, -8, -76, -149, -185, -10, -54, -135, -22, -174, -42, -48, -85, -244, -109, -192, -87, -10, -238, -191, -204, -96, -159, -197, -109, -129, -215, -154, -61, -69, -79, -60, -184, -94, -0, -0, -18, -40, -73, -68, -65, -84, -251, -200, -49, -192, -115, -192, -7, -170, -232, -252, -188, -200, -239, -98, -69, -76, -178, -159, -226, -101, -30, -163, -64, -238, -201, -149, -251, -36, -53, -118, -111, -205, -218, -2, -82, -57, -115, -13, -73, -149, -203, -195, -16, -194, -81, -33, -132, -111, -134, -16, -38, -133, -16, -30, -144, -237, -23, -92, -54, -2, -252, -135, -108, -31, -194, -175, -22, -220, -207, -247, -101, -94, -73, -175, -87, -245, -99, -152, -48, -70, -233, -190, -121, -69, -116, -157, -60, -74, -239, -147, -244, -188, -164, -93, -36, -253, -45, -79, -132, -173, -253, -247, -195, -54, -201, -88, -92, -182, -181, -221, -16, -5, -94, -17, -154, -40, -52, -130, -226, -102, -201, -40, -48, -231, -142, -119, -201, -12, -30, -81, -243, -40, -230, -17, -115, -138, -164, -207, -135, -16, -138, -180, -113, -79, -203, -28, -58, -59, -88, -212, -175, -141, -68, -156, -45, -233, -187, -192, -191, -66, -8, -249, -101, -94, -146, -81, -75, -182, -41, -247, -255, -132, -16, -238, -4, -8, -33, -220, -86, -210, -222, -161, -50, -195, -82, -144, -52, -81, -210, -190, -181, -123, -92, -99, -104, -186, -139, -136, -249, -178, -164, -78, -48, -1, -233, -28, -10, -220, -165, -176, -181, -235, -109, -148, -44, -59, -49, -161, -237, -212, -204, -249, -233, -84, -11, -102, -125, -219, -166, -62, -55, -5, -4, -151, -47, -254, -26, -161, -75, -53, -106, -77, -192, -220, -237, -30, -197, -150, -150, -221, -229, -1, -174, -243, -0, -126, -94, -196, -0, -31, -193, -92, -164, -14, -192, -92, -167, -86, -195, -118, -167, -140, -213, -185, -152, -207, -101, -31, -196, -116, -253, -223, -143, -208, -140, -245, -249, -189, -116, -183, -76, -204, -104, -244, -12, -166, -60, -121, -47, -166, -175, -56, -168, -162, -204, -21, -88, -58, -150, -40, -99, -81, -99, -107, -122, -42, -182, -164, -143, -188, -191, -9, -192, -16, -239, -99, -18, -141, -90, -78, -59, -47, -102, -163, -152, -137, -41, -121, -26, -219, -72, -42, -17, -121, -128, -66, -135, -11, -44, -140, -60, -187, -12, -188, -60, -70, -151, -43, -179, -47, -145, -120, -60, -44, -173, -203, -101, -164, -133, -87, -127, -145, -1, -69, -213, -233, -116, -153, -24, -153, -196, -173, -233, -73, -216, -146, -62, -55, -2, -108, -134, -105, -23, -155, -121, -232, -14, -173, -251, -57, -10, -140, -115, -173, -193, -31, -32, -235, -236, -121, -120, -17, -3, -228, -202, -36, -41, -130, -176, -244, -48, -49, -171, -92, -20, -245, -159, -96, -80, -157, -149, -78, -166, -109, -35, -199, -0, -19, -177, -81, -114, -136, -215, -113, -141, -250, -254, -11, -27, -233, -222, -234, -117, -157, -69, -36, -99, -121, -107, -240, -7, -200, -206, -77, -15, -116, -243, -67, -248, -215, -250, -78, -44, -213, -250, -54, -36, -6, -110, -118, -11, -18, -157, -76, -157, -54, -197, -27, -121, -127, -76, -203, -247, -146, -255, -47, -220, -168, -185, -77, -96, -166, -246, -71, -49, -211, -251, -27, -254, -28, -27, -20, -208, -110, -129, -5, -235, -118, -204, -193, -73, -50, -90, -190, -18, -48, -109, -219, -11, -192, -189, -152, -121, -177, -27, -6, -184, -16, -211, -78, -189, -234, -204, -244, -101, -250, -16, -54, -141, -237, -154, -113, -72, -230, -124, -15, -224, -225, -8, -93, -170, -55, -242, -54, -152, -60, -180, -4, -182, -81, -228, -255, -244, -250, -25, -34, -125, -168, -210, -4, -62, -132, -237, -243, -180, -24, -150, -225, -116, -124, -147, -70, -160, -124, -111, -251, -57, -2, -164, -59, -153, -38, -49, -74, -174, -204, -22, -68, -92, -179, -107, -246, -47, -41, -9, -86, -174, -76, -21, -3, -60, -5, -28, -219, -77, -191, -134, -8, -129, -35, -21, -84, -44, -237, -48, -129, -105, -111, -42, -132, -202, -26, -140, -50, -214, -135, -227, -39, -241, -189, -4, -122, -241, -92, -221, -0, -19, -176, -103, -0, -127, -198, -183, -252, -105, -82, -73, -45, -6, -160, -98, -89, -84, -179, -221, -221, -72, -136, -129, -115, -250, -170, -165, -93, -170, -147, -105, -42, -163, -252, -5, -243, -143, -92, -15, -91, -218, -165, -134, -163, -245, -53, -112, -5, -147, -181, -78, -195, -150, -151, -69, -94, -198, -173, -53, -86, -185, -44, -170, -81, -23, -152, -67, -106, -101, -12, -92, -98, -125, -169, -78, -166, -169, -140, -50, -3, -247, -247, -119, -134, -25, -145, -12, -144, -105, -247, -195, -20, -152, -181, -251, -221, -145, -249, -177, -180, -104, -51, -48, -97, -235, -253, -177, -23, -226, -12, -144, -18, -3, -55, -104, -132, -42, -123, -193, -36, -56, -153, -214, -96, -148, -251, -49, -211, -243, -242, -62, -26, -140, -40, -6, -192, -4, -191, -15, -99, -182, -128, -37, -48, -79, -237, -59, -122, -217, -96, -210, -15, -129, -217, -165, -31, -194, -188, -84, -214, -197, -252, -214, -138, -24, -32, -37, -6, -46, -153, -1, -106, -60, -75, -10, -163, -124, -8, -243, -25, -124, -12, -83, -198, -12, -73, -1, -231, -116, -89, -134, -159, -236, -140, -92, -217, -63, -204, -185, -101, -136, -74, -155, -193, -129, -58, -207, -98, -190, -141, -177, -228, -147, -139, -98, -102, -247, -23, -48, -129, -242, -58, -224, -29, -169, -239, -160, -54, -106, -48, -192, -36, -50, -91, -190, -224, -169, -81, -10, -234, -203, -154, -121, -63, -16, -123, -201, -189, -96, -128, -54, -17, -97, -248, -127, -149, -245, -15, -155, -62, -47, -246, -15, -99, -136, -241, -134, -193, -129, -58, -107, -251, -113, -235, -62, -154, -85, -94, -176, -221, -248, -4, -142, -211, -224, -144, -177, -178, -140, -24, -43, -103, -142, -87, -171, -160, -29, -169, -216, -87, -150, -12, -243, -158, -16, -194, -93, -146, -138, -108, -6, -203, -97, -17, -67, -151, -72, -250, -173, -164, -117, -67, -8, -67, -34, -151, -36, -29, -144, -169, -239, -30, -89, -130, -201, -131, -218, -238, -244, -160, -31, -152, -92, -170, -88, -73, -55, -118, -81, -247, -171, -210, -32, -179, -241, -204, -18, -218, -38, -49, -112, -35, -45, -141, -91, -42, -195, -79, -150, -180, -130, -164, -119, -135, -16, -126, -26, -203, -197, -236, -88, -89, -210, -3, -185, -250, -74, -61, -170, -154, -160, -81, -170, -216, -28, -138, -126, -136, -71, -52, -248, -203, -46, -203, -187, -115, -129, -164, -203, -100, -182, -236, -107, -20, -73, -247, -170, -116, -59, -122, -45, -43, -95, -139, -72, -101, -248, -141, -100, -161, -227, -119, -96, -22, -213, -162, -140, -34, -111, -104, -112, -90, -221, -222, -123, -104, -167, -12, -249, -46, -104, -84, -26, -141, -128, -207, -97, -42, -229, -53, -49, -147, -241, -173, -37, -50, -64, -74, -58, -180, -36, -59, -186, -211, -86, -90, -249, -48, -255, -132, -231, -177, -37, -232, -62, -152, -4, -253, -229, -46, -250, -119, -87, -86, -136, -164, -66, -149, -142, -121, -55, -95, -238, -178, -210, -144, -20, -59, -216, -166, -22, -71, -103, -206, -163, -129, -58, -88, -112, -75, -212, -70, -80, -11, -36, -26, -61, -82, -127, -8, -76, -177, -242, -83, -204, -64, -113, -63, -190, -45, -125, -132, -46, -245, -5, -39, -219, -209, -83, -0, -156, -138, -5, -184, -238, -131, -249, -213, -191, -72, -36, -147, -119, -141, -254, -229, -25, -126, -98, -74, -255, -128, -77, -137, -120, -241, -2, -135, -248, -123, -235, -8, -129, -209, -64, -29, -76, -11, -120, -15, -150, -0, -59, -26, -167, -153, -4, -18, -141, -30, -77, -127, -8, -138, -87, -11, -73, -47, -56, -21, -249, -250, -138, -218, -109, -90, -95, -9, -93, -158, -225, -119, -239, -178, -221, -128, -237, -199, -52, -141, -138, -64, -29, -108, -201, -184, -191, -51, -242, -119, -128, -101, -155, -182, -219, -169, -176, -107, -163, -71, -164, -206, -126, -41, -70, -122, -193, -0, -71, -210, -239, -157, -185, -26, -0, -120, -19, -150, -243, -32, -57, -197, -111, -214, -43, -120, -182, -209, -67, -182, -11, -70, -95, -247, -192, -29, -225, -56, -92, -210, -33, -178, -93, -73, -158, -144, -229, -228, -27, -49, -192, -100, -158, -3, -36, -221, -46, -233, -53, -153, -160, -89, -187, -146, -70, -70, -143, -145, -134, -30, -141, -0, -61, -79, -162, -221, -20, -216, -74, -98, -18, -230, -86, -215, -60, -7, -51, -13, -141, -30, -253, -6, -245, -157, -51, -187, -74, -38, -229, -245, -245, -60, -137, -118, -83, -96, -78, -60, -155, -54, -45, -159, -21, -42, -30, -151, -180, -49, -150, -219, -174, -112, -99, -167, -130, -78, -52, -94, -163, -250, -11, -78, -205, -36, -242, -93, -89, -14, -222, -53, -36, -45, -77, -220, -10, -217, -11, -125, -65, -146, -60, -132, -37, -153, -56, -20, -216, -147, -72, -242, -199, -58, -72, -21, -62, -67, -8, -219, -68, -242, -17, -55, -106, -48, -201, -232, -225, -180, -201, -155, -75, -37, -180, -155, -204, -0, -137, -245, -181, -173, -47, -72, -158, -82, -176, -112, -181, -89, -152, -175, -193, -184, -46, -159, -35, -137, -1, -134, -5, -46, -9, -175, -236, -12, -240, -28, -112, -68, -1, -221, -170, -192, -245, -152, -110, -225, -62, -96, -255, -8, -77, -7, -165, -105, -221, -72, -183, -142, -181, -173, -47, -168, -195, -0, -151, -96, -145, -193, -151, -23, -140, -78, -117, -219, -237, -139, -3, -106, -109, -164, -14, -249, -152, -139, -210, -165, -88, -36, -238, -22, -192, -187, -35, -52, -144, -150, -214, -173, -85, -235, -88, -13, -134, -74, -102, -128, -12, -205, -130, -152, -71, -111, -227, -47, -216, -219, -61, -154, -68, -79, -169, -190, -33, -245, -197, -57, -237, -12, -96, -231, -138, -250, -96, -112, -86, -175, -253, -137, -103, -39, -155, -157, -198, -206, -207, -63, -77, -119, -83, -79, -219, -12, -181, -16, -230, -15, -240, -44, -166, -8, -186, -22, -248, -65, -23, -245, -65, -13, -79, -41, -170, -125, -37, -219, -201, -75, -84, -231, -197, -249, -67, -140, -175, -168, -47, -207, -0, -69, -105, -221, -166, -147, -9, -65, -195, -28, -71, -186, -201, -98, -214, -54, -67, -157, -137, -169, -208, -215, -240, -247, -114, -15, -5, -89, -66, -18, -235, -131, -4, -79, -169, -204, -253, -42, -95, -201, -234, -188, -68, -41, -92, -82, -231, -197, -213, -96, -128, -148, -180, -110, -211, -128, -236, -246, -40, -221, -166, -177, -107, -155, -161, -166, -228, -126, -176, -131, -40, -200, -18, -146, -88, -31, -12, -94, -126, -70, -61, -165, -186, -69, -158, -91, -178, -38, -204, -89, -5, -101, -146, -236, -212, -12, -172, -34, -254, -140, -185, -92, -149, -249, -3, -252, -65, -210, -51, -178, -188, -185, -23, -73, -138, -249, -184, -223, -39, -219, -242, -165, -131, -85, -84, -226, -56, -82, -53, -36, -170, -125, -115, -235, -91, -101, -90, -194, -14, -158, -145, -45, -73, -219, -66, -52, -91, -56, -9, -70, -188, -50, -154, -65, -15, -29, -66, -88, -63, -132, -112, -91, -8, -97, -98, -8, -161, -200, -175, -44, -233, -197, -133, -16, -58, -46, -214, -219, -134, -16, -230, -13, -33, -148, -229, -34, -216, -222, -105, -198, -133, -16, -78, -8, -33, -196, -50, -127, -156, -39, -233, -176, -206, -212, -35, -233, -147, -178, -77, -160, -138, -112, -129, -164, -159, -73, -42, -218, -233, -35, -153, -161, -18, -231, -207, -71, -37, -45, -151, -57, -31, -39, -115, -254, -232, -6, -41, -158, -82, -79, -200, -156, -104, -86, -148, -169, -240, -99, -106, -234, -20, -26, -137, -180, -88, -185, -84, -59, -245, -252, -62, -140, -149, -234, -165, -243, -50, -64, -9, -93, -178, -117, -44, -5, -36, -154, -91, -157, -182, -114, -254, -4, -190, -128, -173, -96, -58, -66, -219, -100, -42, -194, -215, -43, -250, -135, -183, -187, -38, -176, -22, -240, -32, -240, -153, -138, -50, -149, -70, -188, -66, -26, -210, -99, -229, -82, -237, -212, -251, -49, -32, -20, -205, -78, -182, -156, -240, -236, -93, -129, -244, -229, -93, -219, -12, -53, -63, -38, -8, -62, -225, -245, -157, -22, -107, -183, -70, -125, -175, -99, -91, -249, -150, -102, -11, -39, -33, -114, -41, -133, -70, -164, -7, -85, -38, -189, -56, -10, -208, -232, -109, -212, -0, -195, -164, -47, -24, -46, -144, -96, -196, -75, -161, -17, -137, -177, -114, -35, -29, -140, -112, -125, -65, -219, -32, -193, -136, -87, -70, -147, -223, -147, -38, -101, -231, -142, -145, -142, -182, -189, -105, -251, -226, -158, -221, -5, -82, -140, -120, -213, -52, -36, -198, -202, -245, -27, -62, -4, -127, -25, -91, -103, -191, -72, -197, -14, -89, -140, -112, -125, -65, -219, -32, -193, -136, -151, -66, -35, -18, -99, -229, -250, -13, -224, -40, -108, -101, -50, -1, -75, -56, -89, -149, -76, -42, -105, -149, -82, -163, -253, -70, -12, -69, -130, -169, -25, -115, -72, -189, -39, -161, -174, -9, -254, -129, -150, -230, -17, -232, -26, -212, -216, -185, -131, -132, -240, -112, -224, -91, -153, -227, -111, -84, -220, -31, -162, -253, -243, -235, -183, -147, -176, -33, -99, -134, -62, -121, -121, -151, -88, -95, -35, -134, -34, -205, -212, -188, -31, -80, -169, -43, -192, -18, -64, -156, -76, -127, -54, -191, -170, -6, -105, -89, -179, -182, -0, -206, -247, -227, -205, -128, -239, -69, -104, -190, -207, -64, -98, -165, -243, -137, -120, -182, -96, -75, -160, -104, -218, -213, -130, -190, -13, -155, -190, -160, -87, -240, -15, -109, -124, -63, -219, -236, -26, -152, -143, -218, -134, -249, -227, -28, -205, -134, -157, -57, -221, -143, -127, -31, -161, -121, -53, -59, -4, -247, -27, -77, -24, -138, -244, -24, -139, -13, -128, -202, -116, -183, -206, -0, -133, -155, -89, -116, -131, -70, -95, -6, -105, -105, -216, -230, -41, -56, -46, -163, -139, -169, -139, -31, -80, -205, -45, -209, -219, -68, -8, -129, -16, -194, -151, -66, -8, -139, -251, -223, -137, -37, -241, -124, -29, -164, -169, -94, -165, -93, -53, -120, -197, -50, -8, -152, -95, -193, -129, -50, -27, -77, -105, -254, -162, -146, -58, -90, -221, -158, -166, -142, -198, -112, -171, -204, -20, -48, -123, -58, -200, -209, -124, -31, -216, -210, -143, -207, -39, -226, -64, -129, -69, -20, -61, -142, -185, -91, -173, -74, -65, -86, -210, -145, -10, -10, -84, -175, -192, -58, -152, -215, -210, -129, -37, -101, -193, -164, -247, -198, -35, -32, -169, -219, -211, -144, -184, -220, -162, -70, -118, -45, -50, -187, -112, -17, -17, -22, -169, -16, -18, -253, -250, -60, -152, -38, -110, -42, -166, -208, -24, -178, -153, -196, -72, -3, -105, -234, -217, -75, -171, -158, -5, -179, -104, -30, -139, -41, -233, -150, -232, -93, -143, -149, -190, -220, -98, -4, -106, -12, -201, -8, -138, -20, -8, -141, -62, -143, -79, -194, -86, -57, -189, -203, -185, -171, -100, -245, -236, -244, -148, -121, -29, -203, -23, -12, -176, -69, -131, -126, -108, -130, -173, -253, -241, -81, -180, -216, -199, -144, -196, -229, -22, -137, -217, -181, -250, -9, -127, -200, -45, -242, -199, -57, -154, -77, -129, -5, -48, -199, -147, -194, -36, -74, -88, -142, -223, -117, -75, -238, -167, -216, -223, -83, -212, -179, -47, -146, -152, -151, -209, -127, -192, -241, -41, -180, -145, -178, -99, -189, -124, -212, -16, -151, -21, -2, -87, -151, -84, -232, -114, -148, -193, -100, -89, -86, -139, -87, -178, -127, -77, -58, -215, -34, -158, -245, -191, -252, -241, -108, -132, -16, -110, -10, -33, -188, -44, -105, -99, -21, -36, -190, -192, -172, -109, -71, -75, -90, -172, -164, -173, -20, -1, -47, -69, -61, -123, -147, -164, -99, -48, -185, -102, -107, -224, -240, -146, -54, -123, -15, -18, -151, -91, -140, -64, -141, -33, -150, -24, -105, -217, -252, -113, -132, -110, -83, -204, -89, -51, -186, -247, -31, -3, -123, -238, -60, -133, -201, -28, -165, -31, -4, -197, -2, -94, -138, -122, -118, -101, -44, -170, -231, -37, -76, -183, -80, -168, -134, -247, -169, -235, -44, -26, -200, -1, -85, -35, -64, -150, -240, -30, -224, -184, -196, -74, -187, -218, -235, -151, -8, -34, -52, -233, -243, -87, -90, -155, -219, -96, -49, -244, -11, -39, -244, -173, -208, -157, -155, -97, -200, -28, -138, -233, -11, -254, -66, -131, -152, -196, -58, -12, -208, -183, -229, -150, -119, -104, -208, -190, -194, -5, -116, -105, -157, -79, -107, -243, -101, -204, -148, -59, -201, -255, -162, -158, -74, -9, -12, -80, -41, -224, -141, -36, -84, -189, -195, -172, -226, -229, -44, -153, -99, -227, -143, -37, -45, -33, -233, -247, -254, -215, -43, -188, -222, -79, -217, -33, -132, -208, -86, -82, -169, -141, -36, -237, -229, -123, -249, -172, -221, -82, -157, -195, -134, -217, -66, -96, -8, -97, -86, -8, -225, -216, -16, -194, -10, -178, -132, -77, -61, -223, -233, -123, -14, -69, -227, -32, -218, -57, -6, -192, -210, -68, -182, -119, -105, -177, -254, -74, -25, -192, -233, -42, -167, -0, -90, -206, -8, -134, -109, -206, -176, -101, -201, -253, -228, -32, -218, -132, -182, -160, -197, -192, -216, -130, -54, -234, -77, -163, -192, -95, -49, -19, -232, -39, -11, -238, -39, -101, -9, -199, -76, -161, -215, -3, -167, -71, -238, -181, -38, -3, -144, -184, -239, -79, -42, -48, -111, -222, -51, -129, -69, -128, -229, -170, -75, -116, -213, -86, -33, -3, -84, -49, -54, -166, -71, -184, -25, -216, -190, -164, -254, -77, -48, -33, -29, -103, -216, -238, -130, -77, -169, -145, -37, -28, -183, -153, -3, -255, -136, -220, -75, -226, -252, -218, -220, -91, -94, -87, -210, -22, -114, -152, -13, -99, -18, -230, -149, -219, -120, -107, -218, -196, -62, -149, -49, -64, -41, -99, -99, -91, -218, -126, -25, -75, -233, -51, -242, -224, -35, -197, -117, -196, -13, -69, -173, -48, -0, -17, -148, -212, -85, -26, -59, -55, -28, -72, -125, -15, -37, -229, -199, -151, -61, -115, -207, -224, -29, -239, -54, -244, -185, -235, -41, -32, -181, -158, -145, -138, -34, -6, -32, -81, -7, -50, -34, -25, -128, -52, -93, -121, -229, -151, -75, -194, -252, -149, -242, -5, -213, -25, -37, -218, -0, -53, -236, -239, -101, -253, -175, -98, -254, -12, -205, -235, -192, -129, -64, -163, -101, -110, -62, -89, -244, -78, -88, -182, -203, -87, -129, -235, -155, -84, -168, -4, -93, -121, -136, -32, -66, -115, -115, -8, -97, -5, -191, -189, -124, -8, -97, -200, -198, -201, -53, -176, -179, -6, -187, -187, -247, -18, -41, -1, -182, -173, -32, -132, -48, -69, -210, -30, -50, -29, -78, -101, -36, -114, -229, -71, -128, -237, -85, -123, -18, -182, -9, -65, -97, -182, -201, -178, -17, -32, -71, -215, -122, -194, -201, -72, -63, -170, -70, -146, -202, -81, -194, -233, -170, -178, -143, -125, -17, -91, -33, -188, -132, -173, -148, -186, -86, -2, -181, -48, -2, -44, -129, -153, -231, -143, -37, -109, -251, -219, -78, -220, -226, -154, -157, -107, -121, -129, -104, -5, -73, -215, -132, -16, -166, -135, -16, -158, -80, -3, -208, -255, -132, -147, -93, -127, -221, -164, -101, -31, -123, -70, -22, -145, -252, -14, -89, -100, -241, -144, -200, -228, -126, -79, -55, -146, -214, -145, -237, -168, -126, -86, -8, -225, -153, -42, -226, -16, -194, -164, -206, -223, -144, -155, -88, -98, -35, -112, -75, -88, -89, -69, -101, -35, -0, -125, -212, -149, -167, -124, -221, -189, -248, -81, -48, -7, -214, -162, -93, -77, -146, -133, -210, -22, -70, -128, -174, -133, -192, -172, -42, -120, -23, -63, -220, -221, -213, -193, -77, -177, -145, -164, -175, -135, -16, -238, -84, -129, -93, -29, -248, -33, -112, -35, -22, -199, -119, -66, -228, -254, -246, -126, -255, -111, -180, -227, -14, -93, -58, -74, -52, -96, -146, -247, -72, -122, -168, -224, -222, -235, -41, -126, -18, -12, -56, -210, -118, -19, -101, -212, -110, -144, -106, -217, -151, -157, -74, -71, -194, -46, -91, -192, -124, -254, -127, -94, -226, -249, -128, -110, -195, -20, -29, -75, -2, -255, -172, -232, -71, -233, -23, -87, -99, -148, -72, -250, -114, -125, -100, -123, -30, -216, -175, -97, -91, -203, -96, -94, -73, -39, -97, -171, -155, -104, -230, -175, -170, -17, -0, -155, -255, -191, -65, -133, -207, -66, -45, -164, -50, -64, -69, -29, -41, -206, -16, -159, -192, -66, -157, -174, -35, -238, -80, -49, -37, -118, -92, -208, -223, -210, -47, -183, -6, -3, -164, -102, -43, -189, -25, -248, -97, -23, -253, -153, -142, -169, -113, -175, -39, -18, -43, -145, -161, -171, -98, -128, -23, -48, -231, -150, -230, -249, -129, -11, -30, -160, -231, -217, -41, -49, -149, -236, -155, -253, -75, -24, -178, -124, -193, -166, -134, -37, -48, -245, -237, -109, -93, -182, -85, -249, -117, -215, -100, -128, -153, -148, -251, -18, -116, -173, -152, -162, -109, -29, -126, -141, -134, -251, -197, -0, -231, -96, -6, -167, -223, -19, -223, -36, -114, -107, -76, -6, -184, -145, -76, -166, -172, -134, -109, -165, -142, -18, -165, -52, -53, -218, -234, -169, -117, -175, -167, -232, -23, -3, -140, -52, -212, -249, -114, -49, -69, -217, -222, -37, -245, -204, -81, -12, -208, -52, -52, -44, -201, -178, -54, -135, -33, -73, -122, -87, -181, -228, -61, -95, -183, -83, -192, -136, -7, -213, -89, -41, -91, -217, -89, -188, -162, -15, -251, -249, -124, -220, -181, -16, -212, -214, -151, -91, -103, -42, -1, -62, -6, -252, -185, -219, -54, -11, -234, -190, -24, -184, -164, -23, -117, -167, -52, -222, -218, -206, -226, -37, -109, -28, -136, -45, -55, -95, -109, -145, -1, -250, -102, -85, -196, -18, -99, -79, -163, -100, -163, -7, -239, -199, -195, -192, -123, -234, -220, -243, -251, -189, -241, -232, -162, -69, -87, -109, -74, -60, -94, -170, -166, -24, -103, -176, -85, -48, -75, -216, -16, -6, -192, -118, -61, -123, -8, -243, -2, -62, -51, -161, -47, -201, -95, -110, -27, -240, -175, -191, -52, -49, -68, -83, -6, -160, -194, -163, -171, -107, -80, -189, -62, -77, -181, -101, -23, -122, -188, -144, -232, -188, -225, -109, -196, -24, -96, -77, -175, -127, -93, -224, -209, -212, -103, -235, -23, -176, -192, -208, -115, -170, -41, -251, -136, -178, -47, -50, -71, -151, -106, -167, -46, -165, -105, -3, -69, -12, -224, -247, -174, -195, -242, -23, -124, -164, -151, -125, -104, -2, -204, -234, -250, -209, -4, -58, -40, -214, -184, -22, -222, -171, -131, -236, -23, -118, -128, -164, -159, -43, -109, -227, -230, -86, -80, -244, -16, -109, -60, -92, -8, -97, -43, -73, -235, -75, -58, -173, -155, -122, -98, -160, -194, -233, -195, -251, -191, -62, -166, -210, -126, -58, -82, -197, -210, -50, -235, -226, -176, -99, -118, -96, -72, -8, -97, -150, -164, -163, -74, -104, -231, -24, -0, -43, -134, -16, -30, -149, -25, -163, -26, -167, -108, -47, -65, -138, -211, -199, -143, -37, -157, -44, -41, -230, -21, -20, -100, -73, -183, -135, -29, -35, -198, -65, -178, -101, -252, -10, -184, -79, -210, -197, -138, -4, -111, -96, -18, -255, -195, -46, -40, -78, -136, -220, -47, -253, -194, -19, -179, -170, -255, -44, -132, -240, -235, -16, -66, -76, -216, -251, -183, -164, -158, -239, -108, -78, -66, -130, -173, -178, -20, -238, -115, -44, -66, -8, -27, -87, -144, -156, -39, -105, -127, -73, -79, -75, -186, -66, -150, -218, -61, -139, -54, -220, -186, -162, -123, -47, -59, -110, -147, -57, -150, -244, -26, -209, -80, -249, -44, -230, -74, -6, -72, -192, -24, -217, -143, -60, -83, -17, -205, -94, -8, -33, -187, -55, -112, -211, -31, -170, -76, -155, -120, -165, -164, -126, -164, -154, -171, -100, -128, -236, -222, -193, -147, -128, -189, -122, -222, -165, -22, -225, -14, -163, -77, -172, -133, -31, -151, -244, -11, -73, -87, -169, -32, -190, -175, -45, -41, -187, -0, -23, -72, -90, -145, -18, -115, -112, -75, -72, -103, -0, -89, -186, -182, -170, -117, -247, -38, -146, -110, -240, -211, -235, -138, -214, -248, -35, -29, -33, -132, -203, -67, -8, -111, -247, -29, -74, -174, -168, -91, -30, -184, -149, -72, -196, -83, -141, -246, -159, -147, -116, -156, -164, -83, -154, -214, -145, -216, -206, -86, -77, -125, -59, -27, -131, -26, -182, -236, -94, -46, -3, -187, -69, -197, -26, -124, -34, -112, -107, -147, -178, -13, -250, -176, -89, -183, -245, -164, -54, -54, -29, -88, -186, -47, -141, -205, -1, -24, -78, -38, -196, -92, -225, -198, -99, -187, -169, -22, -186, -231, -183, -129, -172, -16, -56, -70, -115, -192, -254, -0, -48, -160, -167, -207, -7, -148, -0, -127, -146, -116, -73, -8, -225, -59, -125, -239, -88, -187, -120, -80, -210, -235, -146, -142, -239, -219, -16, -142, -5, -60, -12, -91, -74, -214, -84, -96, -134, -144, -29, -178, -140, -144, -185, -183, -9, -102, -101, -91, -40, -114, -111, -54, -18, -219, -25, -246, -105, -168, -31, -200, -239, -24, -18, -29, -1, -24, -65, -14, -32, -238, -172, -17, -219, -86, -78, -33, -132, -155, -37, -61, -37, -105, -136, -199, -174, -99, -130, -122, -31, -26, -150, -12, -103, -178, -174, -77, -207, -221, -48, -107, -18, -3, -168, -100, -15, -190, -178, -47, -203, -231, -178, -31, -3, -209, -31, -172, -71, -248, -179, -236, -135, -142, -225, -181, -94, -230, -37, -42, -250, -65, -49, -155, -192, -196, -94, -181, -219, -13, -146, -100, -128, -16, -194, -78, -37, -117, -148, -77, -27, -231, -200, -18, -77, -237, -89, -191, -107, -141, -113, -155, -164, -227, -187, -173, -36, -22, -176, -218, -5, -222, -144, -134, -238, -250, -57, -18, -208, -181, -16, -24, -141, -51, -27, -184, -183, -143, -36, -1, -63, -174, -223, -181, -198, -120, -70, -150, -237, -108, -196, -32, -132, -240, -238, -58, -244, -190, -196, -36, -132, -176, -97, -238, -250, -169, -146, -246, -13, -33, -188, -189, -173, -190, -205, -113, -171, -128, -4, -228, -183, -182, -157, -19, -81, -212, -255, -117, -36, -253, -178, -205, -134, -178, -230, -224, -57, -253, -165, -117, -176, -148, -70, -136, -173, -189, -10, -69, -239, -188, -100, -196, -120, -151, -164, -99, -218, -236, -195, -220, -104, -14, -126, -135, -76, -14, -72, -134, -11, -111, -255, -47, -114, -125, -68, -9, -111, -158, -48, -163, -219, -13, -169, -7, -161, -47, -12, -80, -177, -115, -120, -219, -216, -70, -102, -228, -169, -131, -55, -36, -197, -182, -100, -107, -69, -120, -115, -219, -65, -97, -144, -235, -112, -98, -142, -51, -7, -251, -50, -43, -154, -116, -194, -173, -107, -43, -202, -150, -173, -117, -240, -134, -164, -151, -242, -23, -235, -10, -111, -37, -24, -177, -211, -235, -28, -199, -0, -146, -94, -46, -185, -247, -37, -73, -159, -117, -107, -91, -12, -111, -2, -222, -28, -209, -5, -204, -82, -124, -4, -104, -5, -69, -140, -228, -186, -147, -5, -134, -115, -191, -133, -57, -142, -1, -202, -132, -213, -16, -194, -206, -21, -197, -255, -216, -33, -205, -93, -143, -142, -0, -255, -23, -48, -199, -49, -64, -83, -84, -172, -114, -138, -100, -128, -57, -2, -221, -172, -224, -230, -198, -85, -64, -19, -204, -82, -100, -4, -0, -78, -165, -96, -71, -180, -185, -5, -255, -103, -70, -128, -50, -132, -16, -22, -45, -184, -213, -186, -226, -101, -20, -35, -16, -20, -196, -252, -187, -103, -211, -26, -53, -234, -105, -197, -186, -215, -79, -140, -142, -0, -134, -34, -141, -92, -173, -108, -105, -115, -145, -54, -117, -20, -163, -24, -197, -40, -70, -49, -138, -81, -140, -98, -20, -163, -24, -197, -40, -70, -49, -138, -81, -140, -98, -20, -163, -24, -197, -40, -70, -49, -138, -81, -204, -37, -248, -255, -129, -51, -157, -250, -111, -139, -157, -153, -0, -0, -0, -0, -73, -69, -78, -68, -174, -66, -96, -130, -}; -/* clang-format on */ diff --git a/scene/resources/default_theme/picker_cursor.png b/scene/resources/default_theme/picker_cursor.png Binary files differnew file mode 100644 index 0000000000..2f403492d2 --- /dev/null +++ b/scene/resources/default_theme/picker_cursor.png diff --git a/scene/resources/default_theme/theme_data.h b/scene/resources/default_theme/theme_data.h index 5d4dbd0758..7d747e3c9e 100644 --- a/scene/resources/default_theme/theme_data.h +++ b/scene/resources/default_theme/theme_data.h @@ -74,6 +74,10 @@ static const unsigned char dropdown_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x8, 0x8, 0x4, 0x0, 0x0, 0x0, 0x6e, 0x6, 0x76, 0x0, 0x0, 0x0, 0x0, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x60, 0xf8, 0xc0, 0xcc, 0x0, 0x2, 0x60, 0x16, 0x98, 0x78, 0x67, 0x8, 0x81, 0x6f, 0x4d, 0xde, 0x9a, 0x0, 0x5, 0xde, 0x3a, 0x3d, 0xfc, 0x8f, 0x80, 0xaf, 0xba, 0x18, 0xde, 0x29, 0x2, 0x19, 0xbf, 0x61, 0x2, 0x6f, 0x62, 0x18, 0x3e, 0xb0, 0xbd, 0x97, 0x4, 0x32, 0xff, 0x80, 0xb9, 0xb1, 0x20, 0x93, 0xc0, 0x42, 0x8, 0x2e, 0x54, 0xe8, 0x9d, 0xdc, 0x9b, 0x54, 0x10, 0xb, 0x21, 0xc4, 0x4, 0x63, 0x1, 0x0, 0x86, 0x1f, 0x3b, 0x1e, 0x92, 0x22, 0x3f, 0x40, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; +static const unsigned char ellipsis_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0xe, 0x0, 0x0, 0x0, 0x8, 0x8, 0x6, 0x0, 0x0, 0x0, 0xc9, 0x11, 0xce, 0xcc, 0x0, 0x0, 0x0, 0x4, 0x73, 0x42, 0x49, 0x54, 0x8, 0x8, 0x8, 0x8, 0x7c, 0x8, 0x64, 0x88, 0x0, 0x0, 0x0, 0x78, 0x49, 0x44, 0x41, 0x54, 0x18, 0x95, 0x95, 0xd1, 0x31, 0xa, 0xc2, 0x50, 0x10, 0x4, 0xd0, 0xb7, 0x1f, 0xf, 0x60, 0x67, 0xa, 0xf, 0x12, 0x6f, 0x60, 0xe9, 0x51, 0x5, 0x3d, 0x44, 0xee, 0x61, 0xa1, 0xa5, 0xf6, 0x81, 0xb5, 0xc8, 0x47, 0x82, 0x84, 0x4f, 0xb2, 0xdd, 0xb0, 0x33, 0xb3, 0xb3, 0x4c, 0x40, 0x66, 0xee, 0x71, 0xc2, 0x1, 0x3b, 0xcb, 0x33, 0xe2, 0x85, 0x21, 0x22, 0xde, 0x51, 0x45, 0x97, 0x86, 0x60, 0xc9, 0xe0, 0x5a, 0xea, 0xa5, 0xb5, 0x22, 0x95, 0xdb, 0x97, 0x1a, 0xf, 0x6e, 0xb8, 0xcf, 0x8, 0x2d, 0xdc, 0x95, 0xd9, 0x22, 0xfe, 0x9c, 0x9b, 0x38, 0x32, 0xf3, 0x8c, 0xe3, 0x86, 0xa8, 0xf0, 0x28, 0x18, 0x4c, 0xf, 0xaf, 0x9d, 0x11, 0x43, 0xf0, 0xab, 0xa3, 0x47, 0xa7, 0x5d, 0xc7, 0xd3, 0x54, 0xc7, 0xe7, 0xb, 0xb9, 0xce, 0x1f, 0xc6, 0x2d, 0x99, 0x55, 0xc7, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; + static const unsigned char error_icon_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xb, 0x13, 0x0, 0x0, 0xb, 0x13, 0x1, 0x0, 0x9a, 0x9c, 0x18, 0x0, 0x0, 0x0, 0x2, 0x62, 0x4b, 0x47, 0x44, 0x0, 0xff, 0x87, 0x8f, 0xcc, 0xbf, 0x0, 0x0, 0x0, 0xe, 0x49, 0x44, 0x41, 0x54, 0x28, 0xcf, 0x63, 0x60, 0x18, 0x5, 0xa3, 0x0, 0x1, 0x0, 0x2, 0x10, 0x0, 0x1, 0x14, 0xc2, 0xc0, 0x92, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; @@ -266,6 +270,10 @@ static const unsigned char panel_bg_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x8, 0x1, 0x3, 0x0, 0x0, 0x0, 0xfe, 0xc1, 0x2c, 0xc8, 0x0, 0x0, 0x0, 0x6, 0x50, 0x4c, 0x54, 0x45, 0x25, 0x25, 0x2a, 0x35, 0x32, 0x3b, 0x4a, 0x73, 0x58, 0x4a, 0x0, 0x0, 0x0, 0xa, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x40, 0x3, 0x0, 0x0, 0x10, 0x0, 0x1, 0xb3, 0xac, 0xe2, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; +static const unsigned char picker_cursor_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0xc, 0x8, 0x4, 0x0, 0x0, 0x0, 0xfc, 0x7c, 0x94, 0x6c, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xe, 0xc3, 0x0, 0x0, 0xe, 0xc3, 0x1, 0xc7, 0x6f, 0xa8, 0x64, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x6e, 0x6b, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x9b, 0xee, 0x3c, 0x1a, 0x0, 0x0, 0x0, 0xb9, 0x49, 0x44, 0x41, 0x54, 0x18, 0xd3, 0x6d, 0x8f, 0x3d, 0x8a, 0xc2, 0x50, 0x18, 0x45, 0xcf, 0x6b, 0x92, 0x2a, 0x19, 0xd4, 0xa4, 0x72, 0x47, 0x3, 0x42, 0xc0, 0x9f, 0x55, 0x44, 0x17, 0x24, 0x88, 0xee, 0x24, 0x53, 0x4d, 0x7e, 0xa, 0xbf, 0x94, 0xd6, 0x71, 0x5, 0xf2, 0x5e, 0x7f, 0x2d, 0xa2, 0xa2, 0xe0, 0x29, 0xef, 0xb9, 0xcd, 0x1, 0x40, 0xb1, 0x76, 0x6a, 0x14, 0x14, 0xd4, 0x68, 0xab, 0x98, 0x11, 0xcd, 0xd5, 0xef, 0x9b, 0xac, 0x27, 0x10, 0x32, 0x3b, 0xb4, 0x32, 0xcd, 0xc7, 0x77, 0xff, 0xfb, 0xc7, 0xc0, 0x92, 0x84, 0x84, 0x82, 0xcb, 0xa2, 0x92, 0x29, 0x46, 0xbb, 0x7d, 0xc3, 0xc0, 0x94, 0x27, 0x13, 0x86, 0x63, 0xa7, 0x12, 0xb5, 0x59, 0xcf, 0x8a, 0x77, 0xd6, 0xb9, 0xa9, 0x46, 0xde, 0x5, 0x92, 0xf, 0x91, 0x3a, 0x2f, 0xff, 0x4d, 0xfc, 0x38, 0xaf, 0x1b, 0x6a, 0x33, 0xa3, 0xf8, 0x10, 0x9b, 0xfc, 0xac, 0x1a, 0x6d, 0xf, 0x2d, 0x17, 0x26, 0xaf, 0x79, 0xc6, 0xf5, 0xd4, 0xa9, 0x44, 0xb1, 0x6c, 0x51, 0x31, 0xb0, 0x26, 0x25, 0x65, 0xc3, 0xb5, 0xa8, 0x64, 0x8a, 0xc6, 0x40, 0x3b, 0x76, 0xb9, 0xb9, 0xe0, 0x42, 0x7e, 0x3e, 0x75, 0x8f, 0x40, 0x0, 0x45, 0x2a, 0x55, 0xcb, 0xcb, 0xeb, 0x5f, 0xa5, 0x22, 0x80, 0x3b, 0xa0, 0x2c, 0x6c, 0xa1, 0x40, 0x2f, 0xda, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; + static const unsigned char popup_bg_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0xa2, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3b, 0x3b, 0x43, 0x42, 0x42, 0x4b, 0x3e, 0x3e, 0x47, 0x3e, 0x3e, 0x46, 0x41, 0x41, 0x4a, 0x0, 0x0, 0x0, 0x3d, 0x3d, 0x45, 0x3b, 0x3b, 0x43, 0x3a, 0x3a, 0x42, 0x38, 0x38, 0x41, 0x37, 0x37, 0x3e, 0x36, 0x36, 0x3d, 0x35, 0x35, 0x3c, 0x0, 0x0, 0x0, 0x38, 0x38, 0x40, 0x38, 0x38, 0x40, 0x31, 0x31, 0x38, 0x34, 0x34, 0x3b, 0x34, 0x34, 0x3b, 0x39, 0x39, 0x3f, 0x31, 0x31, 0x38, 0x2f, 0x2f, 0x36, 0x2d, 0x2d, 0x33, 0x2c, 0x2c, 0x32, 0x2b, 0x2b, 0x31, 0x2a, 0x2a, 0x31, 0x2a, 0x2a, 0x30, 0x29, 0x29, 0x30, 0x29, 0x29, 0x2f, 0x28, 0x28, 0x2e, 0x28, 0x28, 0x2d, 0x27, 0x27, 0x2d, 0x27, 0x27, 0x2c, 0x29, 0x29, 0x2e, 0x26, 0x26, 0x2c, 0x36, 0xc6, 0xc8, 0x93, 0x0, 0x0, 0x0, 0x28, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x1, 0x3, 0x5, 0x8, 0xa, 0xb, 0x4, 0x13, 0x19, 0x1f, 0x22, 0x23, 0x16, 0x27, 0x35, 0x3f, 0x45, 0x46, 0x94, 0xf5, 0xfa, 0xfb, 0xf5, 0x40, 0xfc, 0xfb, 0xfb, 0xfb, 0xfb, 0xfc, 0xfc, 0x1a, 0xf5, 0xf6, 0x95, 0xfa, 0xfb, 0xf4, 0x94, 0x71, 0xda, 0xac, 0x92, 0x0, 0x0, 0x0, 0x7f, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x65, 0x8f, 0x35, 0x82, 0xc3, 0x0, 0xc, 0x4, 0x77, 0x24, 0x85, 0xba, 0xe3, 0xff, 0xff, 0xee, 0xca, 0x74, 0x41, 0xdb, 0x32, 0xf3, 0x94, 0x82, 0x85, 0x10, 0x1d, 0x92, 0xb2, 0x3, 0x8e, 0x95, 0x77, 0x93, 0x6c, 0x28, 0xed, 0x15, 0x54, 0x67, 0xa6, 0x41, 0x3e, 0x8, 0x9c, 0xc3, 0xf4, 0xf2, 0xf6, 0x2a, 0x80, 0xf8, 0x44, 0x2d, 0x79, 0x2d, 0x20, 0xe0, 0x2, 0xa8, 0xc3, 0x2e, 0x6f, 0xc, 0x9e, 0x4c, 0x3c, 0x21, 0x4, 0xd8, 0xf0, 0x2, 0x28, 0x24, 0xcd, 0x3, 0xa9, 0x19, 0x64, 0xce, 0x83, 0x4c, 0x45, 0xe6, 0x69, 0x1a, 0xd8, 0xe9, 0x99, 0x96, 0x7f, 0x77, 0x37, 0x59, 0x83, 0xcc, 0xef, 0x7f, 0x89, 0x1f, 0x8e, 0xbf, 0x95, 0xd3, 0x1d, 0xf0, 0xff, 0x7a, 0x63, 0x7e, 0x86, 0xcb, 0x73, 0x8c, 0x5e, 0xee, 0xca, 0xb1, 0xad, 0x5f, 0x3, 0xaf, 0xdb, 0x49, 0x94, 0x4b, 0x90, 0x40, 0xdf, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp index c04b271d81..8550af8bcb 100644 --- a/scene/resources/environment.cpp +++ b/scene/resources/environment.cpp @@ -1329,7 +1329,7 @@ void Environment::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_density", PROPERTY_HINT_RANGE, "0,1,0.0001,or_greater"), "set_volumetric_fog_density", "get_volumetric_fog_density"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "volumetric_fog_light", PROPERTY_HINT_COLOR_NO_ALPHA), "set_volumetric_fog_light", "get_volumetric_fog_light"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_light_energy", PROPERTY_HINT_RANGE, "0,1024,0.01,or_greater"), "set_volumetric_fog_light_energy", "get_volumetric_fog_light_energy"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_gi_inject", PROPERTY_HINT_EXP_RANGE, "0.00,16,0.01"), "set_volumetric_fog_gi_inject", "get_volumetric_fog_gi_inject"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_gi_inject", PROPERTY_HINT_RANGE, "0.00,16,0.01,exp"), "set_volumetric_fog_gi_inject", "get_volumetric_fog_gi_inject"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_length", PROPERTY_HINT_RANGE, "0,1024,0.01,or_greater"), "set_volumetric_fog_length", "get_volumetric_fog_length"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_detail_spread", PROPERTY_HINT_EXP_EASING, "0.01,16,0.01"), "set_volumetric_fog_detail_spread", "get_volumetric_fog_detail_spread"); ADD_SUBGROUP("Temporal Reprojection", "volumetric_fog_temporal_reprojection_"); diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index 6f87c524d8..032171847d 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -813,7 +813,7 @@ Size2 Font::get_string_size(const String &p_text, int p_size) const { if (cache.has(hash)) { buffer = cache.get(hash); } else { - buffer.instance(); + buffer.instantiate(); int size = p_size <= 0 ? data[0]->get_base_size() : p_size; buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); cache.insert(hash, buffer); @@ -838,7 +838,7 @@ Size2 Font::get_multiline_string_size(const String &p_text, float p_width, int p if (cache_wrap.has(wrp_hash)) { lines_buffer = cache_wrap.get(wrp_hash); } else { - lines_buffer.instance(); + lines_buffer.instantiate(); int size = p_size <= 0 ? data[0]->get_base_size() : p_size; lines_buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); lines_buffer->set_width(p_width); @@ -870,7 +870,7 @@ void Font::draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_t if (cache.has(hash)) { buffer = cache.get(hash); } else { - buffer.instance(); + buffer.instantiate(); int size = p_size <= 0 ? data[0]->get_base_size() : p_size; buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); cache.insert(hash, buffer); @@ -905,7 +905,7 @@ void Font::draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const S if (cache_wrap.has(wrp_hash)) { lines_buffer = cache_wrap.get(wrp_hash); } else { - lines_buffer.instance(); + lines_buffer.instantiate(); int size = p_size <= 0 ? data[0]->get_base_size() : p_size; lines_buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); lines_buffer->set_width(p_width); @@ -1041,7 +1041,7 @@ RES ResourceFormatLoaderFont::load(const String &p_path, const String &p_origina } Ref<FontData> dfont; - dfont.instance(); + dfont.instantiate(); dfont->load_resource(p_path); if (r_error) { @@ -1096,11 +1096,11 @@ RES ResourceFormatLoaderCompatFont::load(const String &p_path, const String &p_o } Ref<FontData> dfont; - dfont.instance(); + dfont.instantiate(); dfont->load_resource(p_path); Ref<Font> font; - font.instance(); + font.instantiate(); font->add_data(dfont); if (r_error) { diff --git a/scene/resources/height_map_shape_3d.cpp b/scene/resources/height_map_shape_3d.cpp index 5593bb766f..de5da944bc 100644 --- a/scene/resources/height_map_shape_3d.cpp +++ b/scene/resources/height_map_shape_3d.cpp @@ -41,10 +41,10 @@ Vector<Vector3> HeightMapShape3D::get_debug_mesh_lines() const { Vector2 size(map_width - 1, map_depth - 1); Vector2 start = size * -0.5; - const real_t *r = map_data.ptr(); + const float *r = map_data.ptr(); // reserve some memory for our points.. - points.resize(((map_width - 1) * map_depth * 2) + (map_width * (map_depth - 1) * 2)); + points.resize(((map_width - 1) * map_depth * 2) + (map_width * (map_depth - 1) * 2) + ((map_width - 1) * (map_depth - 1) * 2)); // now set our points int r_offset = 0; @@ -65,6 +65,11 @@ Vector<Vector3> HeightMapShape3D::get_debug_mesh_lines() const { points.write[w_offset++] = Vector3(height.x, r[r_offset + map_width - 1], height.z + 1.0); } + if ((w != map_width - 1) && (d != map_depth - 1)) { + points.write[w_offset++] = Vector3(height.x + 1.0, r[r_offset], height.z); + points.write[w_offset++] = Vector3(height.x, r[r_offset + map_width - 1], height.z + 1.0); + } + height.x += 1.0; } @@ -100,7 +105,7 @@ void HeightMapShape3D::set_map_width(int p_new) { int new_size = map_width * map_depth; map_data.resize(map_width * map_depth); - real_t *w = map_data.ptrw(); + float *w = map_data.ptrw(); while (was_size < new_size) { w[was_size++] = 0.0; } @@ -124,7 +129,7 @@ void HeightMapShape3D::set_map_depth(int p_new) { int new_size = map_width * map_depth; map_data.resize(new_size); - real_t *w = map_data.ptrw(); + float *w = map_data.ptrw(); while (was_size < new_size) { w[was_size++] = 0.0; } @@ -146,8 +151,8 @@ void HeightMapShape3D::set_map_data(PackedFloat32Array p_new) { } // copy - real_t *w = map_data.ptrw(); - const real_t *r = p_new.ptr(); + float *w = map_data.ptrw(); + const float *r = p_new.ptr(); for (int i = 0; i < size; i++) { float val = r[i]; w[i] = val; @@ -189,7 +194,7 @@ void HeightMapShape3D::_bind_methods() { HeightMapShape3D::HeightMapShape3D() : Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_HEIGHTMAP)) { map_data.resize(map_width * map_depth); - real_t *w = map_data.ptrw(); + float *w = map_data.ptrw(); w[0] = 0.0; w[1] = 0.0; w[2] = 0.0; diff --git a/scene/resources/height_map_shape_3d.h b/scene/resources/height_map_shape_3d.h index 6fc88cff90..1219791c56 100644 --- a/scene/resources/height_map_shape_3d.h +++ b/scene/resources/height_map_shape_3d.h @@ -39,8 +39,8 @@ class HeightMapShape3D : public Shape3D { int map_width = 2; int map_depth = 2; PackedFloat32Array map_data; - float min_height = 0.0; - float max_height = 0.0; + real_t min_height = 0.0; + real_t max_height = 0.0; protected: static void _bind_methods(); diff --git a/scene/resources/immediate_mesh.cpp b/scene/resources/immediate_mesh.cpp new file mode 100644 index 0000000000..ebe65605f8 --- /dev/null +++ b/scene/resources/immediate_mesh.cpp @@ -0,0 +1,416 @@ +/*************************************************************************/ +/* immediate_mesh.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "immediate_mesh.h" + +void ImmediateMesh::surface_begin(PrimitiveType p_primitive, const Ref<Material> &p_material) { + ERR_FAIL_COND_MSG(surface_active, "Already creating a new surface."); + active_surface_data.primitive = p_primitive; + active_surface_data.material = p_material; + surface_active = true; +} +void ImmediateMesh::surface_set_color(const Color &p_color) { + ERR_FAIL_COND_MSG(!surface_active, "Not creating any surface. Use surface_begin() to do it."); + + if (!uses_colors) { + colors.resize(vertices.size()); + for (uint32_t i = 0; i < colors.size(); i++) { + colors[i] = p_color; + } + uses_colors = true; + } + + current_color = p_color; +} +void ImmediateMesh::surface_set_normal(const Vector3 &p_normal) { + ERR_FAIL_COND_MSG(!surface_active, "Not creating any surface. Use surface_begin() to do it."); + + if (!uses_normals) { + normals.resize(vertices.size()); + for (uint32_t i = 0; i < normals.size(); i++) { + normals[i] = p_normal; + } + uses_normals = true; + } + + current_normal = p_normal; +} +void ImmediateMesh::surface_set_tangent(const Plane &p_tangent) { + ERR_FAIL_COND_MSG(!surface_active, "Not creating any surface. Use surface_begin() to do it."); + if (!uses_tangents) { + tangents.resize(vertices.size()); + for (uint32_t i = 0; i < tangents.size(); i++) { + tangents[i] = p_tangent; + } + uses_tangents = true; + } + + current_tangent = p_tangent; +} +void ImmediateMesh::surface_set_uv(const Vector2 &p_uv) { + ERR_FAIL_COND_MSG(!surface_active, "Not creating any surface. Use surface_begin() to do it."); + if (!uses_uvs) { + uvs.resize(vertices.size()); + for (uint32_t i = 0; i < uvs.size(); i++) { + uvs[i] = p_uv; + } + uses_uvs = true; + } + + current_uv = p_uv; +} +void ImmediateMesh::surface_set_uv2(const Vector2 &p_uv2) { + ERR_FAIL_COND_MSG(!surface_active, "Not creating any surface. Use surface_begin() to do it."); + if (!uses_uv2s) { + uv2s.resize(vertices.size()); + for (uint32_t i = 0; i < uv2s.size(); i++) { + uv2s[i] = p_uv2; + } + uses_uv2s = true; + } + + current_uv2 = p_uv2; +} +void ImmediateMesh::surface_add_vertex(const Vector3 &p_vertex) { + ERR_FAIL_COND_MSG(!surface_active, "Not creating any surface. Use surface_begin() to do it."); + ERR_FAIL_COND_MSG(vertices.size() && active_surface_data.vertex_2d, "Can't mix 2D and 3D vertices in a surface."); + + if (uses_colors) { + colors.push_back(current_color); + } + if (uses_normals) { + normals.push_back(current_normal); + } + if (uses_tangents) { + tangents.push_back(current_tangent); + } + if (uses_uvs) { + uvs.push_back(current_uv); + } + if (uses_uv2s) { + uv2s.push_back(current_uv2); + } + vertices.push_back(p_vertex); +} + +void ImmediateMesh::surface_add_vertex_2d(const Vector2 &p_vertex) { + ERR_FAIL_COND_MSG(!surface_active, "Not creating any surface. Use surface_begin() to do it."); + ERR_FAIL_COND_MSG(vertices.size() && !active_surface_data.vertex_2d, "Can't mix 2D and 3D vertices in a surface."); + + if (uses_colors) { + colors.push_back(current_color); + } + if (uses_normals) { + normals.push_back(current_normal); + } + if (uses_tangents) { + tangents.push_back(current_tangent); + } + if (uses_uvs) { + uvs.push_back(current_uv); + } + if (uses_uv2s) { + uv2s.push_back(current_uv2); + } + Vector3 v(p_vertex.x, p_vertex.y, 0); + vertices.push_back(v); + + active_surface_data.vertex_2d = true; +} +void ImmediateMesh::surface_end() { + ERR_FAIL_COND_MSG(!surface_active, "Not creating any surface. Use surface_begin() to do it."); + ERR_FAIL_COND_MSG(!vertices.size(), "No vertices were added, surface cant be created."); + + uint32_t format = ARRAY_FORMAT_VERTEX; + + uint32_t vertex_stride = 0; + if (active_surface_data.vertex_2d) { + format |= ARRAY_FLAG_USE_2D_VERTICES; + vertex_stride = sizeof(float) * 2; + } else { + vertex_stride = sizeof(float) * 3; + } + + uint32_t normal_offset = 0; + if (uses_normals) { + format |= ARRAY_FORMAT_NORMAL; + normal_offset = vertex_stride; + vertex_stride += sizeof(uint32_t); + } + uint32_t tangent_offset = 0; + if (uses_tangents) { + format |= ARRAY_FORMAT_TANGENT; + tangent_offset += vertex_stride; + vertex_stride += sizeof(uint32_t); + } + + AABB aabb; + + { + surface_vertex_create_cache.resize(vertex_stride * vertices.size()); + uint8_t *surface_vertex_ptr = surface_vertex_create_cache.ptrw(); + for (uint32_t i = 0; i < vertices.size(); i++) { + { + float *vtx = (float *)&surface_vertex_ptr[i * vertex_stride]; + vtx[0] = vertices[i].x; + vtx[1] = vertices[i].y; + if (!active_surface_data.vertex_2d) { + vtx[2] = vertices[i].z; + } + if (i == 0) { + aabb.position = vertices[i]; + } else { + aabb.expand_to(vertices[i]); + } + } + if (uses_normals) { + uint32_t *normal = (uint32_t *)&surface_vertex_ptr[i * vertex_stride + normal_offset]; + + Vector3 n = normals[i] * Vector3(0.5, 0.5, 0.5) + Vector3(0.5, 0.5, 0.5); + + uint32_t value = 0; + value |= CLAMP(int(n.x * 1023.0), 0, 1023); + value |= CLAMP(int(n.y * 1023.0), 0, 1023) << 10; + value |= CLAMP(int(n.z * 1023.0), 0, 1023) << 20; + + *normal = value; + } + if (uses_tangents) { + uint32_t *tangent = (uint32_t *)&surface_vertex_ptr[i * vertex_stride + tangent_offset]; + Plane t = tangents[i]; + uint32_t value = 0; + value |= CLAMP(int((t.normal.x * 0.5 + 0.5) * 1023.0), 0, 1023); + value |= CLAMP(int((t.normal.y * 0.5 + 0.5) * 1023.0), 0, 1023) << 10; + value |= CLAMP(int((t.normal.z * 0.5 + 0.5) * 1023.0), 0, 1023) << 20; + if (t.d > 0) { + value |= 3 << 30; + } + + *tangent = value; + } + } + } + + if (uses_colors || uses_uvs || uses_uv2s) { + uint32_t attribute_stride = 0; + + if (uses_colors) { + format |= ARRAY_FORMAT_COLOR; + attribute_stride += sizeof(uint8_t) * 4; + } + uint32_t uv_offset = 0; + if (uses_uvs) { + format |= ARRAY_FORMAT_TEX_UV; + uv_offset = attribute_stride; + attribute_stride += sizeof(float) * 2; + } + uint32_t uv2_offset = 0; + if (uses_uv2s) { + format |= ARRAY_FORMAT_TEX_UV2; + uv2_offset = attribute_stride; + attribute_stride += sizeof(float) * 2; + } + + surface_attribute_create_cache.resize(vertices.size() * attribute_stride); + + uint8_t *surface_attribute_ptr = surface_attribute_create_cache.ptrw(); + + for (uint32_t i = 0; i < vertices.size(); i++) { + if (uses_colors) { + uint8_t *color8 = (uint8_t *)&surface_attribute_ptr[i * attribute_stride]; + + color8[0] = uint8_t(CLAMP(colors[i].r * 255.0, 0.0, 255.0)); + color8[1] = uint8_t(CLAMP(colors[i].g * 255.0, 0.0, 255.0)); + color8[2] = uint8_t(CLAMP(colors[i].b * 255.0, 0.0, 255.0)); + color8[3] = uint8_t(CLAMP(colors[i].a * 255.0, 0.0, 255.0)); + } + if (uses_uvs) { + float *uv = (float *)&surface_attribute_ptr[i * attribute_stride + uv_offset]; + + uv[0] = uvs[i].x; + uv[1] = uvs[i].y; + } + + if (uses_uv2s) { + float *uv2 = (float *)&surface_attribute_ptr[i * attribute_stride + uv2_offset]; + + uv2[0] = uv2s[i].x; + uv2[1] = uv2s[i].y; + } + } + } + + RS::SurfaceData sd; + + sd.primitive = RS::PrimitiveType(active_surface_data.primitive); + sd.format = format; + sd.vertex_data = surface_vertex_create_cache; + if (uses_colors || uses_uvs || uses_uv2s) { + sd.attribute_data = surface_attribute_create_cache; + } + sd.vertex_count = vertices.size(); + sd.aabb = aabb; + if (active_surface_data.material.is_valid()) { + sd.material = active_surface_data.material->get_rid(); + } + + RS::get_singleton()->mesh_add_surface(mesh, sd); + + active_surface_data.aabb = aabb; + + active_surface_data.format = format; + active_surface_data.array_len = vertices.size(); + + surfaces.push_back(active_surface_data); + + colors.clear(); + normals.clear(); + tangents.clear(); + uvs.clear(); + uv2s.clear(); + vertices.clear(); + + uses_colors = false; + uses_normals = false; + uses_tangents = false; + uses_uvs = false; + uses_uv2s = false; + + surface_active = false; +} + +void ImmediateMesh::clear_surfaces() { + RS::get_singleton()->mesh_clear(mesh); + surfaces.clear(); + surface_active = false; + + colors.clear(); + normals.clear(); + tangents.clear(); + uvs.clear(); + uv2s.clear(); + vertices.clear(); + + uses_colors = false; + uses_normals = false; + uses_tangents = false; + uses_uvs = false; + uses_uv2s = false; +} + +int ImmediateMesh::get_surface_count() const { + return surfaces.size(); +} +int ImmediateMesh::surface_get_array_len(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, int(surfaces.size()), -1); + return surfaces[p_idx].array_len; +} +int ImmediateMesh::surface_get_array_index_len(int p_idx) const { + return 0; +} +bool ImmediateMesh::surface_is_softbody_friendly(int p_idx) const { + return false; +} +Array ImmediateMesh::surface_get_arrays(int p_surface) const { + ERR_FAIL_INDEX_V(p_surface, int(surfaces.size()), Array()); + return RS::get_singleton()->mesh_surface_get_arrays(mesh, p_surface); +} +Array ImmediateMesh::surface_get_blend_shape_arrays(int p_surface) const { + return Array(); +} +Dictionary ImmediateMesh::surface_get_lods(int p_surface) const { + return Dictionary(); +} +uint32_t ImmediateMesh::surface_get_format(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, int(surfaces.size()), 0); + return surfaces[p_idx].format; +} +Mesh::PrimitiveType ImmediateMesh::surface_get_primitive_type(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, int(surfaces.size()), PRIMITIVE_MAX); + return surfaces[p_idx].primitive; +} +void ImmediateMesh::surface_set_material(int p_idx, const Ref<Material> &p_material) { + ERR_FAIL_INDEX(p_idx, int(surfaces.size())); + surfaces[p_idx].material = p_material; + RID mat; + if (p_material.is_valid()) { + mat = p_material->get_rid(); + } + RS::get_singleton()->mesh_surface_set_material(mesh, p_idx, mat); +} +Ref<Material> ImmediateMesh::surface_get_material(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, int(surfaces.size()), Ref<Material>()); + return surfaces[p_idx].material; +} +int ImmediateMesh::get_blend_shape_count() const { + return 0; +} +StringName ImmediateMesh::get_blend_shape_name(int p_index) const { + return StringName(); +} +void ImmediateMesh::set_blend_shape_name(int p_index, const StringName &p_name) { +} + +AABB ImmediateMesh::get_aabb() const { + AABB aabb; + for (uint32_t i = 0; i < surfaces.size(); i++) { + if (i == 0) { + aabb = surfaces[i].aabb; + } else { + aabb.merge(surfaces[i].aabb); + } + } + return aabb; +} + +void ImmediateMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("surface_begin", "primitive", "material"), &ImmediateMesh::surface_begin, DEFVAL(Ref<Material>())); + ClassDB::bind_method(D_METHOD("surface_set_color", "color"), &ImmediateMesh::surface_set_color); + ClassDB::bind_method(D_METHOD("surface_set_normal", "normal"), &ImmediateMesh::surface_set_normal); + ClassDB::bind_method(D_METHOD("surface_set_tangent", "tangent"), &ImmediateMesh::surface_set_tangent); + ClassDB::bind_method(D_METHOD("surface_set_uv", "uv"), &ImmediateMesh::surface_set_uv); + ClassDB::bind_method(D_METHOD("surface_set_uv2", "uv2"), &ImmediateMesh::surface_set_uv2); + ClassDB::bind_method(D_METHOD("surface_add_vertex", "vertex"), &ImmediateMesh::surface_add_vertex); + ClassDB::bind_method(D_METHOD("surface_add_vertex_2d", "vertex"), &ImmediateMesh::surface_add_vertex_2d); + ClassDB::bind_method(D_METHOD("surface_end"), &ImmediateMesh::surface_end); + + ClassDB::bind_method(D_METHOD("clear_surfaces"), &ImmediateMesh::clear_surfaces); +} + +RID ImmediateMesh::get_rid() const { + return mesh; +} + +ImmediateMesh::ImmediateMesh() { + mesh = RS::get_singleton()->mesh_create(); +} +ImmediateMesh::~ImmediateMesh() { + RS::get_singleton()->free(mesh); +} diff --git a/scene/resources/immediate_mesh.h b/scene/resources/immediate_mesh.h new file mode 100644 index 0000000000..7010d40719 --- /dev/null +++ b/scene/resources/immediate_mesh.h @@ -0,0 +1,117 @@ +/*************************************************************************/ +/* immediate_mesh.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef IMMEDIATE_MESH_H +#define IMMEDIATE_MESH_H + +#include "core/templates/local_vector.h" +#include "scene/resources/mesh.h" + +class ImmediateMesh : public Mesh { + GDCLASS(ImmediateMesh, Mesh) + + RID mesh; + + bool uses_colors = false; + bool uses_normals = false; + bool uses_tangents = false; + bool uses_uvs = false; + bool uses_uv2s = false; + + Color current_color; + Vector3 current_normal; + Plane current_tangent; + Vector2 current_uv; + Vector2 current_uv2; + + LocalVector<Color> colors; + LocalVector<Vector3> normals; + LocalVector<Plane> tangents; + LocalVector<Vector2> uvs; + LocalVector<Vector2> uv2s; + LocalVector<Vector3> vertices; + + struct Surface { + PrimitiveType primitive; + Ref<Material> material; + bool vertex_2d = false; + int array_len = 0; + uint32_t format = 0; + AABB aabb; + }; + + LocalVector<Surface> surfaces; + + bool surface_active = false; + Surface active_surface_data; + + Vector<uint8_t> surface_vertex_create_cache; + Vector<uint8_t> surface_attribute_create_cache; + +protected: + static void _bind_methods(); + +public: + void surface_begin(PrimitiveType p_primitive, const Ref<Material> &p_material = Ref<Material>()); + void surface_set_color(const Color &p_color); + void surface_set_normal(const Vector3 &p_normal); + void surface_set_tangent(const Plane &p_tangent); + void surface_set_uv(const Vector2 &p_uv); + void surface_set_uv2(const Vector2 &p_uv2); + void surface_add_vertex(const Vector3 &p_vertex); + void surface_add_vertex_2d(const Vector2 &p_vertex); + void surface_end(); + + void clear_surfaces(); + + virtual int get_surface_count() const override; + virtual int surface_get_array_len(int p_idx) const override; + virtual int surface_get_array_index_len(int p_idx) const override; + virtual bool surface_is_softbody_friendly(int p_idx) const override; + virtual Array surface_get_arrays(int p_surface) const override; + virtual Array surface_get_blend_shape_arrays(int p_surface) const override; + virtual Dictionary surface_get_lods(int p_surface) const override; + virtual uint32_t surface_get_format(int p_idx) const override; + virtual PrimitiveType surface_get_primitive_type(int p_idx) const override; + virtual void surface_set_material(int p_idx, const Ref<Material> &p_material) override; + virtual Ref<Material> surface_get_material(int p_idx) const override; + virtual int get_blend_shape_count() const override; + virtual StringName get_blend_shape_name(int p_index) const override; + virtual void set_blend_shape_name(int p_index, const StringName &p_name) override; + + virtual AABB get_aabb() const override; + + virtual RID get_rid() const override; + + ImmediateMesh(); + ~ImmediateMesh(); +}; + +#endif // IMMEDIATEMESH_H diff --git a/scene/resources/line_shape_2d.h b/scene/resources/line_shape_2d.h index 9f0405ad29..210a1aa9e6 100644 --- a/scene/resources/line_shape_2d.h +++ b/scene/resources/line_shape_2d.h @@ -36,7 +36,8 @@ class LineShape2D : public Shape2D { GDCLASS(LineShape2D, Shape2D); - Vector2 normal = Vector2(0, 1); + // LineShape2D is often used for one-way platforms, where the normal pointing up makes sense. + Vector2 normal = Vector2(0, -1); real_t distance = 0.0; void _update_shape(); diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 0d02bde90d..a264c2d1eb 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -77,7 +77,7 @@ RID Material::get_rid() const { void Material::_validate_property(PropertyInfo &property) const { if (!_can_do_next_pass() && property.name == "next_pass") { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } } @@ -130,7 +130,7 @@ bool ShaderMaterial::_set(const StringName &p_name, const Variant &p_value) { } } if (pr) { - RenderingServer::get_singleton()->material_set_param(_get_material(), pr, p_value); + set_shader_param(pr, p_value); return true; } } @@ -152,7 +152,12 @@ bool ShaderMaterial::_get(const StringName &p_name, Variant &r_ret) const { } if (pr) { - r_ret = RenderingServer::get_singleton()->material_get_param(_get_material(), pr); + const Map<StringName, Variant>::Element *E = param_cache.find(pr); + if (E) { + r_ret = E->get(); + } else { + r_ret = Variant(); + } return true; } } @@ -219,11 +224,31 @@ Ref<Shader> ShaderMaterial::get_shader() const { } void ShaderMaterial::set_shader_param(const StringName &p_param, const Variant &p_value) { - RS::get_singleton()->material_set_param(_get_material(), p_param, p_value); + if (p_value.get_type() == Variant::NIL) { + param_cache.erase(p_param); + RS::get_singleton()->material_set_param(_get_material(), p_param, Variant()); + } else { + param_cache[p_param] = p_value; + if (p_value.get_type() == Variant::OBJECT) { + RID tex_rid = p_value; + if (tex_rid == RID()) { + param_cache.erase(p_param); + RS::get_singleton()->material_set_param(_get_material(), p_param, Variant()); + } else { + RS::get_singleton()->material_set_param(_get_material(), p_param, tex_rid); + } + } else { + RS::get_singleton()->material_set_param(_get_material(), p_param, p_value); + } + } } Variant ShaderMaterial::get_shader_param(const StringName &p_param) const { - return RS::get_singleton()->material_get_param(_get_material(), p_param); + if (param_cache.has(p_param)) { + return param_cache[p_param]; + } else { + return Variant(); + } } void ShaderMaterial::_shader_changed() { @@ -345,7 +370,6 @@ void BaseMaterial3D::init_shaders() { shader_names->refraction_texture_channel = "refraction_texture_channel"; shader_names->transmittance_color = "transmittance_color"; - shader_names->transmittance_curve = "transmittance_curve"; shader_names->transmittance_depth = "transmittance_depth"; shader_names->transmittance_boost = "transmittance_boost"; @@ -505,9 +529,6 @@ void BaseMaterial3D::_update_shader() { case DIFFUSE_LAMBERT_WRAP: code += ",diffuse_lambert_wrap"; break; - case DIFFUSE_OREN_NAYAR: - code += ",diffuse_oren_nayar"; - break; case DIFFUSE_TOON: code += ",diffuse_toon"; break; @@ -543,6 +564,9 @@ void BaseMaterial3D::_update_shader() { if (flags[FLAG_DISABLE_DEPTH_TEST]) { code += ",depth_test_disabled"; } + if (flags[FLAG_PARTICLE_TRAILS_MODE]) { + code += ",particle_trails"; + } if (shading_mode == SHADING_MODE_PER_VERTEX) { code += ",vertex_lighting"; } @@ -672,7 +696,7 @@ void BaseMaterial3D::_update_shader() { code += "uniform sampler2D texture_flowmap : hint_aniso," + texfilter_str + ";\n"; } if (features[FEATURE_AMBIENT_OCCLUSION]) { - code += "uniform sampler2D texture_ambient_occlusion : hint_white;\n"; + code += "uniform sampler2D texture_ambient_occlusion : hint_white, " + texfilter_str + ";\n"; code += "uniform vec4 ao_texture_channel;\n"; code += "uniform float ao_light_affect;\n"; } @@ -692,7 +716,6 @@ void BaseMaterial3D::_update_shader() { code += "uniform vec4 transmittance_color : hint_color;\n"; code += "uniform float transmittance_depth;\n"; code += "uniform sampler2D texture_subsurface_transmittance : hint_white," + texfilter_str + ";\n"; - code += "uniform float transmittance_curve;\n"; code += "uniform float transmittance_boost;\n"; } @@ -822,23 +845,33 @@ void BaseMaterial3D::_update_shader() { code += "\tTANGENT+= vec3(1.0,0.0,0.0) * abs(NORMAL.z);\n"; code += "\tTANGENT = normalize(TANGENT);\n"; - code += "\tBINORMAL = vec3(0.0,-1.0,0.0) * abs(NORMAL.x);\n"; - code += "\tBINORMAL+= vec3(0.0,0.0,1.0) * abs(NORMAL.y);\n"; - code += "\tBINORMAL+= vec3(0.0,-1.0,0.0) * abs(NORMAL.z);\n"; + code += "\tBINORMAL = vec3(0.0,1.0,0.0) * abs(NORMAL.x);\n"; + code += "\tBINORMAL+= vec3(0.0,0.0,-1.0) * abs(NORMAL.y);\n"; + code += "\tBINORMAL+= vec3(0.0,1.0,0.0) * abs(NORMAL.z);\n"; code += "\tBINORMAL = normalize(BINORMAL);\n"; } if (flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tuv1_power_normal=pow(abs(NORMAL),vec3(uv1_blend_sharpness));\n"; + if (flags[FLAG_UV1_USE_WORLD_TRIPLANAR]) { + code += "\tuv1_power_normal=pow(abs(mat3(WORLD_MATRIX) * NORMAL),vec3(uv1_blend_sharpness));\n"; + code += "\tuv1_triplanar_pos = (WORLD_MATRIX * vec4(VERTEX, 1.0f)).xyz * uv1_scale + uv1_offset;\n"; + } else { + code += "\tuv1_power_normal=pow(abs(NORMAL),vec3(uv1_blend_sharpness));\n"; + code += "\tuv1_triplanar_pos = VERTEX * uv1_scale + uv1_offset;\n"; + } code += "\tuv1_power_normal/=dot(uv1_power_normal,vec3(1.0));\n"; - code += "\tuv1_triplanar_pos = VERTEX * uv1_scale + uv1_offset;\n"; code += "\tuv1_triplanar_pos *= vec3(1.0,-1.0, 1.0);\n"; } if (flags[FLAG_UV2_USE_TRIPLANAR]) { - code += "\tuv2_power_normal=pow(abs(NORMAL), vec3(uv2_blend_sharpness));\n"; + if (flags[FLAG_UV2_USE_WORLD_TRIPLANAR]) { + code += "\tuv2_power_normal=pow(abs(mat3(WORLD_MATRIX) * NORMAL), vec3(uv2_blend_sharpness));\n"; + code += "\tuv2_triplanar_pos = (WORLD_MATRIX * vec4(VERTEX, 1.0f)).xyz * uv2_scale + uv2_offset;\n"; + } else { + code += "\tuv2_power_normal=pow(abs(NORMAL), vec3(uv2_blend_sharpness));\n"; + code += "\tuv2_triplanar_pos = VERTEX * uv2_scale + uv2_offset;\n"; + } code += "\tuv2_power_normal/=dot(uv2_power_normal,vec3(1.0));\n"; - code += "\tuv2_triplanar_pos = VERTEX * uv2_scale + uv2_offset;\n"; code += "\tuv2_triplanar_pos *= vec3(1.0,-1.0, 1.0);\n"; } @@ -868,6 +901,20 @@ void BaseMaterial3D::_update_shader() { code += "\tvec2 base_uv2 = UV2;\n"; } + if (features[FEATURE_HEIGHT_MAPPING] && flags[FLAG_UV1_USE_TRIPLANAR]) { + // Display both resource name and albedo texture name. + // Materials are often built-in to scenes, so displaying the resource name alone may not be meaningful. + // On the other hand, albedo textures are almost always external to the scene. + if (textures[TEXTURE_ALBEDO].is_valid()) { + WARN_PRINT(vformat("%s (albedo %s): Height mapping is not supported on triplanar materials. Ignoring height mapping in favor of triplanar mapping.", get_path(), textures[TEXTURE_ALBEDO]->get_path())); + } else if (!get_path().is_empty()) { + WARN_PRINT(vformat("%s: Height mapping is not supported on triplanar materials. Ignoring height mapping in favor of triplanar mapping.", get_path())); + } else { + // Resource wasn't saved yet. + WARN_PRINT("Height mapping is not supported on triplanar materials. Ignoring height mapping in favor of triplanar mapping."); + } + } + if (!RenderingServer::get_singleton()->is_low_end() && features[FEATURE_HEIGHT_MAPPING] && !flags[FLAG_UV1_USE_TRIPLANAR]) { //heightmap not supported with triplanar code += "\t{\n"; code += "\t\tvec3 view_dir = normalize(normalize(-VERTEX)*mat3(TANGENT*heightmap_flip.x,-BINORMAL*heightmap_flip.y,NORMAL));\n"; // binormal is negative due to mikktspace, flip 'unflips' it ;-) @@ -878,7 +925,7 @@ void BaseMaterial3D::_update_shader() { code += "\t\tfloat current_layer_depth = 0.0;\n"; code += "\t\tvec2 P = view_dir.xy * heightmap_scale;\n"; code += "\t\tvec2 delta = P / num_layers;\n"; - code += "\t\tvec2 ofs = base_uv;\n"; + code += "\t\tvec2 ofs = base_uv;\n"; if (flags[FLAG_INVERT_HEIGHTMAP]) { code += "\t\tfloat depth = texture(texture_heightmap, ofs).r;\n"; } else { @@ -977,9 +1024,9 @@ void BaseMaterial3D::_update_shader() { code += "\tSPECULAR = specular;\n"; } else { if (flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tfloat orm_tex = triplanar_texture(texture_orm,uv1_power_normal,uv1_triplanar_pos);\n"; + code += "\tvec4 orm_tex = triplanar_texture(texture_orm,uv1_power_normal,uv1_triplanar_pos);\n"; } else { - code += "\tfloat orm_tex = texture(texture_orm,base_uv);\n"; + code += "\tvec4 orm_tex = texture(texture_orm,base_uv);\n"; } code += "\tROUGHNESS = orm_tex.g;\n"; @@ -1019,7 +1066,10 @@ void BaseMaterial3D::_update_shader() { if (features[FEATURE_REFRACTION]) { if (features[FEATURE_NORMAL_MAPPING]) { - code += "\tvec3 ref_normal = normalize( mix(NORMAL,TANGENT * NORMAL_MAP.x + BINORMAL * NORMAL_MAP.y + NORMAL * NORMAL_MAP.z,NORMAL_MAP_DEPTH) );\n"; + code += "\tvec3 unpacked_normal = NORMAL_MAP;\n"; + code += "\tunpacked_normal.xy = unpacked_normal.xy * 2.0 - 1.0;\n"; + code += "\tunpacked_normal.z = sqrt(max(0.0, 1.0 - dot(unpacked_normal.xy, unpacked_normal.xy)));\n"; + code += "\tvec3 ref_normal = normalize( mix(NORMAL,TANGENT * unpacked_normal.x + BINORMAL * unpacked_normal.y + NORMAL * unpacked_normal.z,NORMAL_MAP_DEPTH) );\n"; } else { code += "\tvec3 ref_normal = NORMAL;\n"; } @@ -1167,7 +1217,6 @@ void BaseMaterial3D::_update_shader() { code += "\tSSS_TRANSMITTANCE_COLOR=transmittance_color*trans_color_tex;\n"; code += "\tSSS_TRANSMITTANCE_DEPTH=transmittance_depth;\n"; - code += "\tSSS_TRANSMITTANCE_CURVE=transmittance_curve;\n"; code += "\tSSS_TRANSMITTANCE_BOOST=transmittance_boost;\n"; } @@ -1411,15 +1460,6 @@ float BaseMaterial3D::get_transmittance_depth() const { return transmittance_depth; } -void BaseMaterial3D::set_transmittance_curve(float p_curve) { - transmittance_curve = p_curve; - RS::get_singleton()->material_set_param(_get_material(), shader_names->transmittance_curve, p_curve); -} - -float BaseMaterial3D::get_transmittance_curve() const { - return transmittance_curve; -} - void BaseMaterial3D::set_transmittance_boost(float p_boost) { transmittance_boost = p_boost; RS::get_singleton()->material_set_param(_get_material(), shader_names->transmittance_boost, p_boost); @@ -1587,6 +1627,9 @@ void BaseMaterial3D::set_flag(Flags p_flag, bool p_enabled) { if (p_flag == FLAG_USE_SHADOW_TO_OPACITY || p_flag == FLAG_USE_TEXTURE_REPEAT || p_flag == FLAG_SUBSURFACE_MODE_SKIN || p_flag == FLAG_USE_POINT_SIZE) { notify_property_list_changed(); } + if (p_flag == FLAG_PARTICLE_TRAILS_MODE) { + update_configuration_warning(); + } _queue_shader_change(); } @@ -1680,7 +1723,7 @@ void BaseMaterial3D::_validate_property(PropertyInfo &property) const { _validate_high_end("heightmap", property); if (property.name.begins_with("particles_anim_") && billboard_mode != BILLBOARD_PARTICLES) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name == "billboard_keep_scale" && billboard_mode == BILLBOARD_DISABLED) { @@ -1710,46 +1753,47 @@ void BaseMaterial3D::_validate_property(PropertyInfo &property) const { // alpha scissor slider isn't needed when alpha antialiasing is enabled if (property.name == "alpha_scissor_threshold" && transparency != TRANSPARENCY_ALPHA_SCISSOR) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } // alpha hash scale slider is only needed if transparency is alpha hash if (property.name == "alpha_hash_scale" && transparency != TRANSPARENCY_ALPHA_HASH) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name == "alpha_antialiasing_mode" && !can_select_aa) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } // we can't choose an antialiasing mode if alpha isn't possible if (property.name == "alpha_antialiasing_edge" && !alpha_aa_enabled) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name == "blend_mode" && alpha_aa_enabled) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if ((property.name == "heightmap_min_layers" || property.name == "heightmap_max_layers") && !deep_parallax) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } - if (flags[FLAG_SUBSURFACE_MODE_SKIN] && (property.name == "subsurf_scatter_transmittance_color" || property.name == "subsurf_scatter_transmittance_texture" || property.name == "subsurf_scatter_transmittance_curve")) { - property.usage = 0; + if (flags[FLAG_SUBSURFACE_MODE_SKIN] && (property.name == "subsurf_scatter_transmittance_color" || property.name == "subsurf_scatter_transmittance_texture")) { + property.usage = PROPERTY_USAGE_NONE; } if (orm) { if (property.name == "shading_mode") { - property.hint_string = "Unshaded,PerPixel"; //vertex not supported in ORM mode, since no individual roughness. + // Vertex not supported in ORM mode, since no individual roughness. + property.hint_string = "Unshaded,Per-Pixel"; } if (property.name.begins_with("roughness") || property.name.begins_with("metallic") || property.name.begins_with("ao_texture")) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } } else { if (property.name == "orm_texture") { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } } @@ -1757,47 +1801,47 @@ void BaseMaterial3D::_validate_property(PropertyInfo &property) const { if (shading_mode != SHADING_MODE_PER_VERTEX) { //these may still work per vertex if (property.name.begins_with("ao")) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name.begins_with("emission")) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name.begins_with("metallic")) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name.begins_with("rim")) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name.begins_with("roughness")) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name.begins_with("subsurf_scatter")) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } } //these definitely only need per pixel if (property.name.begins_with("anisotropy")) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name.begins_with("clearcoat")) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name.begins_with("normal")) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name.begins_with("backlight")) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name.begins_with("transmittance")) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } } } @@ -2046,7 +2090,7 @@ BaseMaterial3D::TextureChannel BaseMaterial3D::get_refraction_texture_channel() return refraction_texture_channel; } -RID BaseMaterial3D::get_material_rid_for_2d(bool p_shaded, bool p_transparent, bool p_double_sided, bool p_cut_alpha, bool p_opaque_prepass, bool p_billboard, bool p_billboard_y) { +Ref<Material> BaseMaterial3D::get_material_for_2d(bool p_shaded, bool p_transparent, bool p_double_sided, bool p_cut_alpha, bool p_opaque_prepass, bool p_billboard, bool p_billboard_y, RID *r_shader_rid) { int version = 0; if (p_shaded) { version = 1; @@ -2071,11 +2115,14 @@ RID BaseMaterial3D::get_material_rid_for_2d(bool p_shaded, bool p_transparent, b } if (materials_for_2d[version].is_valid()) { - return materials_for_2d[version]->get_rid(); + if (r_shader_rid) { + *r_shader_rid = materials_for_2d[version]->get_shader_rid(); + } + return materials_for_2d[version]; } Ref<StandardMaterial3D> material; - material.instance(); + material.instantiate(); material->set_shading_mode(p_shaded ? SHADING_MODE_PER_PIXEL : SHADING_MODE_UNSHADED); material->set_transparency(p_transparent ? (p_opaque_prepass ? TRANSPARENCY_ALPHA_DEPTH_PRE_PASS : (p_cut_alpha ? TRANSPARENCY_ALPHA_SCISSOR : TRANSPARENCY_ALPHA)) : TRANSPARENCY_DISABLED); @@ -2089,7 +2136,11 @@ RID BaseMaterial3D::get_material_rid_for_2d(bool p_shaded, bool p_transparent, b materials_for_2d[version] = material; - return materials_for_2d[version]->get_rid(); + if (r_shader_rid) { + *r_shader_rid = materials_for_2d[version]->get_shader_rid(); + } + + return materials_for_2d[version]; } void BaseMaterial3D::set_on_top_of_alpha() { @@ -2158,6 +2209,8 @@ BaseMaterial3D::EmissionOperator BaseMaterial3D::get_emission_operator() const { } RID BaseMaterial3D::get_shader_rid() const { + MutexLock lock(material_mutex); + ((BaseMaterial3D *)this)->_update_shader(); ERR_FAIL_COND_V(!shader_map.has(current_key), RID()); return shader_map[current_key].shader; } @@ -2167,6 +2220,8 @@ Shader::Mode BaseMaterial3D::get_shader_mode() const { } void BaseMaterial3D::_bind_methods() { + static_assert(sizeof(MaterialKey) == 16, "MaterialKey should be 16 bytes"); + ClassDB::bind_method(D_METHOD("set_albedo", "albedo"), &BaseMaterial3D::set_albedo); ClassDB::bind_method(D_METHOD("get_albedo"), &BaseMaterial3D::get_albedo); @@ -2227,9 +2282,6 @@ void BaseMaterial3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_transmittance_depth", "depth"), &BaseMaterial3D::set_transmittance_depth); ClassDB::bind_method(D_METHOD("get_transmittance_depth"), &BaseMaterial3D::get_transmittance_depth); - ClassDB::bind_method(D_METHOD("set_transmittance_curve", "curve"), &BaseMaterial3D::set_transmittance_curve); - ClassDB::bind_method(D_METHOD("get_transmittance_curve"), &BaseMaterial3D::get_transmittance_curve); - ClassDB::bind_method(D_METHOD("set_transmittance_boost", "boost"), &BaseMaterial3D::set_transmittance_boost); ClassDB::bind_method(D_METHOD("get_transmittance_boost"), &BaseMaterial3D::get_transmittance_boost); @@ -2366,19 +2418,19 @@ void BaseMaterial3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_distance_fade_min_distance"), &BaseMaterial3D::get_distance_fade_min_distance); ADD_GROUP("Transparency", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "transparency", PROPERTY_HINT_ENUM, "Disabled,Alpha,Alpha Scissor,Alpha Hash,Depth PrePass"), "set_transparency", "get_transparency"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "transparency", PROPERTY_HINT_ENUM, "Disabled,Alpha,Alpha Scissor,Alpha Hash,Depth Pre-Pass"), "set_transparency", "get_transparency"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "alpha_scissor_threshold", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_alpha_scissor_threshold", "get_alpha_scissor_threshold"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "alpha_hash_scale", PROPERTY_HINT_RANGE, "0,2,0.01"), "set_alpha_hash_scale", "get_alpha_hash_scale"); ADD_PROPERTY(PropertyInfo(Variant::INT, "alpha_antialiasing_mode", PROPERTY_HINT_ENUM, "Disabled,Alpha Edge Blend,Alpha Edge Clip"), "set_alpha_antialiasing", "get_alpha_antialiasing"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "alpha_antialiasing_edge", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_alpha_antialiasing_edge", "get_alpha_antialiasing_edge"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Mix,Add,Sub,Mul"), "set_blend_mode", "get_blend_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Mix,Add,Subtract,Multiply"), "set_blend_mode", "get_blend_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mode", PROPERTY_HINT_ENUM, "Back,Front,Disabled"), "set_cull_mode", "get_cull_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "depth_draw_mode", PROPERTY_HINT_ENUM, "Opaque Only,Always,Never"), "set_depth_draw_mode", "get_depth_draw_mode"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "no_depth_test"), "set_flag", "get_flag", FLAG_DISABLE_DEPTH_TEST); ADD_GROUP("Shading", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "shading_mode", PROPERTY_HINT_ENUM, "Unshaded,PerPixel,PerVertex"), "set_shading_mode", "get_shading_mode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "diffuse_mode", PROPERTY_HINT_ENUM, "Burley,Lambert,Lambert Wrap,Oren Nayar,Toon"), "set_diffuse_mode", "get_diffuse_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "shading_mode", PROPERTY_HINT_ENUM, "Unshaded,Per-Pixel,Per-Vertex"), "set_shading_mode", "get_shading_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "diffuse_mode", PROPERTY_HINT_ENUM, "Burley,Lambert,Lambert Wrap,Toon"), "set_diffuse_mode", "get_diffuse_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "specular_mode", PROPERTY_HINT_ENUM, "SchlickGGX,Blinn,Phong,Toon,Disabled"), "set_specular_mode", "get_specular_mode"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "disable_ambient_light"), "set_flag", "get_flag", FLAG_DISABLE_AMBIENT_LIGHT); @@ -2464,7 +2516,6 @@ void BaseMaterial3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::COLOR, "subsurf_scatter_transmittance_color"), "set_transmittance_color", "get_transmittance_color"); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "subsurf_scatter_transmittance_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture", TEXTURE_SUBSURFACE_TRANSMITTANCE); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "subsurf_scatter_transmittance_depth", PROPERTY_HINT_RANGE, "0.001,8,0.001,or_greater"), "set_transmittance_depth", "get_transmittance_depth"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "subsurf_scatter_transmittance_curve", PROPERTY_HINT_EXP_EASING, "0.01,16,0.01"), "set_transmittance_curve", "get_transmittance_curve"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "subsurf_scatter_transmittance_boost", PROPERTY_HINT_RANGE, "0.00,1.0,0.01"), "set_transmittance_boost", "get_transmittance_boost"); ADD_GROUP("Back Lighting", "backlight_"); @@ -2481,7 +2532,7 @@ void BaseMaterial3D::_bind_methods() { ADD_GROUP("Detail", "detail_"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "detail_enabled"), "set_feature", "get_feature", FEATURE_DETAIL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "detail_mask", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture", TEXTURE_DETAIL_MASK); - ADD_PROPERTY(PropertyInfo(Variant::INT, "detail_blend_mode", PROPERTY_HINT_ENUM, "Mix,Add,Sub,Mul"), "set_detail_blend_mode", "get_detail_blend_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "detail_blend_mode", PROPERTY_HINT_ENUM, "Mix,Add,Subtract,Multiply"), "set_detail_blend_mode", "get_detail_blend_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "detail_uv_layer", PROPERTY_HINT_ENUM, "UV1,UV2"), "set_detail_uv", "get_detail_uv"); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "detail_albedo", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture", TEXTURE_DETAIL_ALBEDO); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "detail_normal", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture", TEXTURE_DETAIL_NORMAL); @@ -2501,7 +2552,7 @@ void BaseMaterial3D::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "uv2_world_triplanar"), "set_flag", "get_flag", FLAG_UV2_USE_WORLD_TRIPLANAR); ADD_GROUP("Sampling", "texture_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_filter", PROPERTY_HINT_ENUM, "Nearest,Linear,MipmapNearest,MipmapLinear,MipmapNearestAniso,MipmapLinearAniso"), "set_texture_filter", "get_texture_filter"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_filter", PROPERTY_HINT_ENUM, "Nearest,Linear,Nearest Mipmap,Linear Mipmap,Nearest Mipmap Aniso.,Linear Mipmap Aniso."), "set_texture_filter", "get_texture_filter"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "texture_repeat"), "set_flag", "get_flag", FLAG_USE_TEXTURE_REPEAT); ADD_GROUP("Shadows", ""); @@ -2524,6 +2575,7 @@ void BaseMaterial3D::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "fixed_size"), "set_flag", "get_flag", FLAG_FIXED_SIZE); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "use_point_size"), "set_flag", "get_flag", FLAG_USE_POINT_SIZE); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "point_size", PROPERTY_HINT_RANGE, "0.1,128,0.1"), "set_point_size", "get_point_size"); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "use_particle_trails"), "set_flag", "get_flag", FLAG_PARTICLE_TRAILS_MODE); ADD_GROUP("Proximity Fade", "proximity_fade_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "proximity_fade_enable"), "set_proximity_fade", "is_proximity_fade_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "proximity_fade_distance", PROPERTY_HINT_RANGE, "0,4096,0.01"), "set_proximity_fade_distance", "get_proximity_fade_distance"); @@ -2625,12 +2677,12 @@ void BaseMaterial3D::_bind_methods() { BIND_ENUM_CONSTANT(FLAG_USE_TEXTURE_REPEAT); BIND_ENUM_CONSTANT(FLAG_INVERT_HEIGHTMAP); BIND_ENUM_CONSTANT(FLAG_SUBSURFACE_MODE_SKIN); + BIND_ENUM_CONSTANT(FLAG_PARTICLE_TRAILS_MODE); BIND_ENUM_CONSTANT(FLAG_MAX); BIND_ENUM_CONSTANT(DIFFUSE_BURLEY); BIND_ENUM_CONSTANT(DIFFUSE_LAMBERT); BIND_ENUM_CONSTANT(DIFFUSE_LAMBERT_WRAP); - BIND_ENUM_CONSTANT(DIFFUSE_OREN_NAYAR); BIND_ENUM_CONSTANT(DIFFUSE_TOON); BIND_ENUM_CONSTANT(SPECULAR_SCHLICK_GGX); @@ -2680,7 +2732,6 @@ BaseMaterial3D::BaseMaterial3D(bool p_orm) : set_backlight(Color(0, 0, 0)); set_transmittance_color(Color(1, 1, 1, 1)); set_transmittance_depth(0.1); - set_transmittance_curve(1.0); set_transmittance_boost(0.0); set_refraction(0.05); set_point_size(1); diff --git a/scene/resources/material.h b/scene/resources/material.h index 70452a5f74..e2838e1399 100644 --- a/scene/resources/material.h +++ b/scene/resources/material.h @@ -79,6 +79,8 @@ class ShaderMaterial : public Material { GDCLASS(ShaderMaterial, Material); Ref<Shader> shader; + Map<StringName, Variant> param_cache; + protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -235,6 +237,7 @@ public: FLAG_USE_TEXTURE_REPEAT, FLAG_INVERT_HEIGHTMAP, FLAG_SUBSURFACE_MODE_SKIN, + FLAG_PARTICLE_TRAILS_MODE, FLAG_MAX }; @@ -242,7 +245,6 @@ public: DIFFUSE_BURLEY, DIFFUSE_LAMBERT, DIFFUSE_LAMBERT_WRAP, - DIFFUSE_OREN_NAYAR, DIFFUSE_TOON, DIFFUSE_MAX }; @@ -305,16 +307,15 @@ private: uint64_t roughness_channel : get_num_bits(TEXTURE_CHANNEL_MAX - 1); uint64_t emission_op : get_num_bits(EMISSION_OP_MAX - 1); uint64_t distance_fade : get_num_bits(DISTANCE_FADE_MAX - 1); - - // flag bitfield - uint64_t feature_mask : FEATURE_MAX - 1; - uint64_t flags : FLAG_MAX - 1; - // booleans uint64_t deep_parallax : 1; uint64_t grow : 1; uint64_t proximity_fade : 1; + // flag bitfield + uint32_t feature_mask; + uint32_t flags; + MaterialKey() { memset(this, 0, sizeof(MaterialKey)); } @@ -390,7 +391,6 @@ private: StringName heightmap_scale; StringName subsurface_scattering_strength; StringName transmittance_color; - StringName transmittance_curve; StringName transmittance_depth; StringName transmittance_boost; StringName backlight; @@ -459,7 +459,6 @@ private: float transmittance_amount; Color transmittance_color; float transmittance_depth; - float transmittance_curve; float transmittance_boost; Color backlight; @@ -603,9 +602,6 @@ public: void set_transmittance_depth(float p_depth); float get_transmittance_depth() const; - void set_transmittance_curve(float p_curve); - float get_transmittance_curve() const; - void set_transmittance_boost(float p_boost); float get_transmittance_boost() const; @@ -739,7 +735,7 @@ public: static void finish_shaders(); static void flush_changes(); - static RID get_material_rid_for_2d(bool p_shaded, bool p_transparent, bool p_double_sided, bool p_cut_alpha, bool p_opaque_prepass, bool p_billboard = false, bool p_billboard_y = false); + static Ref<Material> get_material_for_2d(bool p_shaded, bool p_transparent, bool p_double_sided, bool p_cut_alpha, bool p_opaque_prepass, bool p_billboard = false, bool p_billboard_y = false, RID *r_shader_rid = nullptr); virtual RID get_shader_rid() const override; diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index 1a2b21299a..2f92872ad5 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -30,6 +30,7 @@ #include "mesh.h" +#include "core/math/convex_hull.h" #include "core/templates/pair.h" #include "scene/resources/concave_polygon_shape_3d.h" #include "scene/resources/convex_polygon_shape_3d.h" @@ -221,9 +222,17 @@ Vector<Face3> Mesh::get_faces() const { */ } -Ref<Shape3D> Mesh::create_convex_shape() const { - Vector<Vector3> vertices; +Ref<Shape3D> Mesh::create_convex_shape(bool p_clean, bool p_simplify) const { + if (p_simplify) { + Vector<Ref<Shape3D>> decomposed = convex_decompose(1); + if (decomposed.size() == 1) { + return decomposed[0]; + } else { + ERR_PRINT("Convex shape simplification failed, falling back to simpler process."); + } + } + Vector<Vector3> vertices; for (int i = 0; i < get_surface_count(); i++) { Array a = surface_get_arrays(i); ERR_FAIL_COND_V(a.is_empty(), Ref<ConvexPolygonShape3D>()); @@ -232,6 +241,18 @@ Ref<Shape3D> Mesh::create_convex_shape() const { } Ref<ConvexPolygonShape3D> shape = memnew(ConvexPolygonShape3D); + + if (p_clean) { + Geometry3D::MeshData md; + Error err = ConvexHullComputer::convex_hull(vertices, md); + if (err == OK) { + shape->set_points(md.vertices); + return shape; + } else { + ERR_PRINT("Convex shape cleaning failed, falling back to simpler process."); + } + } + shape->set_points(vertices); return shape; } @@ -543,12 +564,12 @@ void Mesh::clear_cache() const { debug_lines.clear(); } -Vector<Ref<Shape3D>> Mesh::convex_decompose() const { +Vector<Ref<Shape3D>> Mesh::convex_decompose(int p_max_convex_hulls) const { ERR_FAIL_COND_V(!convex_composition_function, Vector<Ref<Shape3D>>()); const Vector<Face3> faces = get_faces(); - Vector<Vector<Face3>> decomposed = convex_composition_function(faces); + Vector<Vector<Face3>> decomposed = convex_composition_function(faces, p_max_convex_hulls); Vector<Ref<Shape3D>> ret; @@ -571,7 +592,7 @@ Vector<Ref<Shape3D>> Mesh::convex_decompose() const { } Ref<ConvexPolygonShape3D> shape; - shape.instance(); + shape.instantiate(); shape->set_points(convex_points); ret.push_back(shape); } @@ -579,160 +600,331 @@ Vector<Ref<Shape3D>> Mesh::convex_decompose() const { return ret; } +int Mesh::get_builtin_bind_pose_count() const { + return 0; +} + +Transform3D Mesh::get_builtin_bind_pose(int p_index) const { + return Transform3D(); +} + Mesh::Mesh() { } -#if 0 -static Vector<uint8_t> _fix_array_compatibility(const Vector<uint8_t> &p_src, uint32_t p_format, uint32_t p_elements) { - enum ArrayType { - OLD_ARRAY_VERTEX = 0, - OLD_ARRAY_NORMAL = 1, - OLD_ARRAY_TANGENT = 2, - OLD_ARRAY_COLOR = 3, - OLD_ARRAY_TEX_UV = 4, - OLD_ARRAY_TEX_UV2 = 5, - OLD_ARRAY_BONES = 6, - OLD_ARRAY_WEIGHTS = 7, - OLD_ARRAY_INDEX = 8, - OLD_ARRAY_MAX = 9 - }; - - enum ArrayFormat { - /* OLD_ARRAY FORMAT FLAGS */ - OLD_ARRAY_FORMAT_VERTEX = 1 << OLD_ARRAY_VERTEX, // mandatory - OLD_ARRAY_FORMAT_NORMAL = 1 << OLD_ARRAY_NORMAL, - OLD_ARRAY_FORMAT_TANGENT = 1 << OLD_ARRAY_TANGENT, - OLD_ARRAY_FORMAT_COLOR = 1 << OLD_ARRAY_COLOR, - OLD_ARRAY_FORMAT_TEX_UV = 1 << OLD_ARRAY_TEX_UV, - OLD_ARRAY_FORMAT_TEX_UV2 = 1 << OLD_ARRAY_TEX_UV2, - OLD_ARRAY_FORMAT_BONES = 1 << OLD_ARRAY_BONES, - OLD_ARRAY_FORMAT_WEIGHTS = 1 << OLD_ARRAY_WEIGHTS, - OLD_ARRAY_FORMAT_INDEX = 1 << OLD_ARRAY_INDEX, - - OLD_ARRAY_COMPRESS_BASE = (OLD_ARRAY_INDEX + 1), - OLD_ARRAY_COMPRESS_NORMAL = 1 << (OLD_ARRAY_NORMAL + OLD_ARRAY_COMPRESS_BASE), - OLD_ARRAY_COMPRESS_TANGENT = 1 << (OLD_ARRAY_TANGENT + OLD_ARRAY_COMPRESS_BASE), - OLD_ARRAY_COMPRESS_COLOR = 1 << (OLD_ARRAY_COLOR + OLD_ARRAY_COMPRESS_BASE), - OLD_ARRAY_COMPRESS_TEX_UV = 1 << (OLD_ARRAY_TEX_UV + OLD_ARRAY_COMPRESS_BASE), - OLD_ARRAY_COMPRESS_TEX_UV2 = 1 << (OLD_ARRAY_TEX_UV2 + OLD_ARRAY_COMPRESS_BASE), - OLD_ARRAY_COMPRESS_INDEX = 1 << (OLD_ARRAY_INDEX + OLD_ARRAY_COMPRESS_BASE), - OLD_ARRAY_COMPRESS_DEFAULT = OLD_ARRAY_COMPRESS_NORMAL | OLD_ARRAY_COMPRESS_TANGENT | OLD_ARRAY_COMPRESS_COLOR | OLD_ARRAY_COMPRESS_TEX_UV | OLD_ARRAY_COMPRESS_TEX_UV2, - - OLD_ARRAY_FLAG_USE_2D_VERTICES = OLD_ARRAY_COMPRESS_INDEX << 1, - OLD_ARRAY_FLAG_USE_DYNAMIC_UPDATE = OLD_ARRAY_COMPRESS_INDEX << 3, - }; - - bool vertex_16bit = p_format & ((1 << (OLD_ARRAY_VERTEX + OLD_ARRAY_COMPRESS_BASE))); - bool has_bones = (p_format & OLD_ARRAY_FORMAT_BONES); - bool bone_8 = has_bones && !(p_format & (OLD_ARRAY_COMPRESS_INDEX << 2)); - bool weight_32 = has_bones && !(p_format & (OLD_ARRAY_COMPRESS_TEX_UV2 << 2)); - - print_line("convert vertex16: " + itos(vertex_16bit) + " convert bone 8 " + itos(bone_8) + " convert weight 32 " + itos(weight_32)); - - if (!vertex_16bit && !bone_8 && !weight_32) { - return p_src; - } +enum OldArrayType { + OLD_ARRAY_VERTEX, + OLD_ARRAY_NORMAL, + OLD_ARRAY_TANGENT, + OLD_ARRAY_COLOR, + OLD_ARRAY_TEX_UV, + OLD_ARRAY_TEX_UV2, + OLD_ARRAY_BONES, + OLD_ARRAY_WEIGHTS, + OLD_ARRAY_INDEX, + OLD_ARRAY_MAX, +}; - bool vertex_2d = (p_format & (OLD_ARRAY_COMPRESS_INDEX << 1)); +enum OldArrayFormat { + /* OLD_ARRAY FORMAT FLAGS */ + OLD_ARRAY_FORMAT_VERTEX = 1 << OLD_ARRAY_VERTEX, // mandatory + OLD_ARRAY_FORMAT_NORMAL = 1 << OLD_ARRAY_NORMAL, + OLD_ARRAY_FORMAT_TANGENT = 1 << OLD_ARRAY_TANGENT, + OLD_ARRAY_FORMAT_COLOR = 1 << OLD_ARRAY_COLOR, + OLD_ARRAY_FORMAT_TEX_UV = 1 << OLD_ARRAY_TEX_UV, + OLD_ARRAY_FORMAT_TEX_UV2 = 1 << OLD_ARRAY_TEX_UV2, + OLD_ARRAY_FORMAT_BONES = 1 << OLD_ARRAY_BONES, + OLD_ARRAY_FORMAT_WEIGHTS = 1 << OLD_ARRAY_WEIGHTS, + OLD_ARRAY_FORMAT_INDEX = 1 << OLD_ARRAY_INDEX, + + OLD_ARRAY_COMPRESS_BASE = (OLD_ARRAY_INDEX + 1), + OLD_ARRAY_COMPRESS_VERTEX = 1 << (OLD_ARRAY_VERTEX + OLD_ARRAY_COMPRESS_BASE), // mandatory + OLD_ARRAY_COMPRESS_NORMAL = 1 << (OLD_ARRAY_NORMAL + OLD_ARRAY_COMPRESS_BASE), + OLD_ARRAY_COMPRESS_TANGENT = 1 << (OLD_ARRAY_TANGENT + OLD_ARRAY_COMPRESS_BASE), + OLD_ARRAY_COMPRESS_COLOR = 1 << (OLD_ARRAY_COLOR + OLD_ARRAY_COMPRESS_BASE), + OLD_ARRAY_COMPRESS_TEX_UV = 1 << (OLD_ARRAY_TEX_UV + OLD_ARRAY_COMPRESS_BASE), + OLD_ARRAY_COMPRESS_TEX_UV2 = 1 << (OLD_ARRAY_TEX_UV2 + OLD_ARRAY_COMPRESS_BASE), + OLD_ARRAY_COMPRESS_BONES = 1 << (OLD_ARRAY_BONES + OLD_ARRAY_COMPRESS_BASE), + OLD_ARRAY_COMPRESS_WEIGHTS = 1 << (OLD_ARRAY_WEIGHTS + OLD_ARRAY_COMPRESS_BASE), + OLD_ARRAY_COMPRESS_INDEX = 1 << (OLD_ARRAY_INDEX + OLD_ARRAY_COMPRESS_BASE), + + OLD_ARRAY_FLAG_USE_2D_VERTICES = OLD_ARRAY_COMPRESS_INDEX << 1, + OLD_ARRAY_FLAG_USE_16_BIT_BONES = OLD_ARRAY_COMPRESS_INDEX << 2, + OLD_ARRAY_FLAG_USE_DYNAMIC_UPDATE = OLD_ARRAY_COMPRESS_INDEX << 3, - uint32_t src_stride = p_src.size() / p_elements; - uint32_t dst_stride = src_stride + (vertex_16bit ? 4 : 0) + (bone_8 ? 4 : 0) - (weight_32 ? 8 : 0); +}; - Vector<uint8_t> ret = p_src; +static Array _convert_old_array(const Array &p_old) { + Array new_array; + new_array.resize(Mesh::ARRAY_MAX); + new_array[Mesh::ARRAY_VERTEX] = p_old[OLD_ARRAY_VERTEX]; + new_array[Mesh::ARRAY_NORMAL] = p_old[OLD_ARRAY_NORMAL]; + new_array[Mesh::ARRAY_TANGENT] = p_old[OLD_ARRAY_TANGENT]; + new_array[Mesh::ARRAY_COLOR] = p_old[OLD_ARRAY_COLOR]; + new_array[Mesh::ARRAY_TEX_UV] = p_old[OLD_ARRAY_TEX_UV]; + new_array[Mesh::ARRAY_TEX_UV2] = p_old[OLD_ARRAY_TEX_UV2]; + new_array[Mesh::ARRAY_BONES] = p_old[OLD_ARRAY_BONES]; + new_array[Mesh::ARRAY_WEIGHTS] = p_old[OLD_ARRAY_WEIGHTS]; + new_array[Mesh::ARRAY_INDEX] = p_old[OLD_ARRAY_INDEX]; + return new_array; +} - ret.resize(dst_stride * p_elements); - { - uint8_t *w = ret.ptrw(); - const uint8_t *r = p_src.ptr(); - - for (uint32_t i = 0; i < p_elements; i++) { - uint32_t remaining = src_stride; - const uint8_t *src = (const uint8_t *)(r + src_stride * i); - uint8_t *dst = (uint8_t *)(w + dst_stride * i); - - if (!vertex_2d) { //3D - if (vertex_16bit) { - float *dstw = (float *)dst; - const uint16_t *srcr = (const uint16_t *)src; - dstw[0] = Math::half_to_float(srcr[0]); - dstw[1] = Math::half_to_float(srcr[1]); - dstw[2] = Math::half_to_float(srcr[2]); - remaining -= 8; - src += 8; - } else { - src += 12; - remaining -= 12; - } - dst += 12; - } else { - if (vertex_16bit) { - float *dstw = (float *)dst; - const uint16_t *srcr = (const uint16_t *)src; - dstw[0] = Math::half_to_float(srcr[0]); - dstw[1] = Math::half_to_float(srcr[1]); - remaining -= 4; - src += 4; - } else { - src += 8; - remaining -= 8; - } - dst += 8; - } +static Mesh::PrimitiveType _old_primitives[7] = { + Mesh::PRIMITIVE_POINTS, + Mesh::PRIMITIVE_LINES, + Mesh::PRIMITIVE_LINE_STRIP, + Mesh::PRIMITIVE_LINES, + Mesh::PRIMITIVE_TRIANGLES, + Mesh::PRIMITIVE_TRIANGLE_STRIP, + Mesh::PRIMITIVE_TRIANGLE_STRIP +}; - if (has_bones) { - remaining -= bone_8 ? 4 : 8; - remaining -= weight_32 ? 16 : 8; - } +void _fix_array_compatibility(const Vector<uint8_t> &p_src, uint32_t p_old_format, uint32_t p_new_format, uint32_t p_elements, Vector<uint8_t> &vertex_data, Vector<uint8_t> &attribute_data, Vector<uint8_t> &skin_data) { + uint32_t dst_vertex_stride; + uint32_t dst_attribute_stride; + uint32_t dst_skin_stride; + uint32_t dst_offsets[Mesh::ARRAY_MAX]; + RenderingServer::get_singleton()->mesh_surface_make_offsets_from_format(p_new_format & (~RS::ARRAY_FORMAT_INDEX), p_elements, 0, dst_offsets, dst_vertex_stride, dst_attribute_stride, dst_skin_stride); - for (uint32_t j = 0; j < remaining; j++) { - dst[j] = src[j]; - } + vertex_data.resize(dst_vertex_stride * p_elements); + attribute_data.resize(dst_attribute_stride * p_elements); + skin_data.resize(dst_skin_stride * p_elements); - if (has_bones) { - dst += remaining; - src += remaining; + uint8_t *dst_vertex_ptr = vertex_data.ptrw(); + uint8_t *dst_attribute_ptr = attribute_data.ptrw(); + uint8_t *dst_skin_ptr = skin_data.ptrw(); - if (bone_8) { - const uint8_t *src_bones = (const uint8_t *)src; - uint16_t *dst_bones = (uint16_t *)dst; + const uint8_t *src_vertex_ptr = p_src.ptr(); + uint32_t src_vertex_stride = p_src.size() / p_elements; - dst_bones[0] = src_bones[0]; - dst_bones[1] = src_bones[1]; - dst_bones[2] = src_bones[2]; - dst_bones[3] = src_bones[3]; + uint32_t src_offset = 0; + for (uint32_t j = 0; j < OLD_ARRAY_INDEX; j++) { + if (!(p_old_format & (1 << j))) { + continue; + } + switch (j) { + case OLD_ARRAY_VERTEX: { + if (p_old_format & OLD_ARRAY_FLAG_USE_2D_VERTICES) { + if (p_old_format & OLD_ARRAY_COMPRESS_VERTEX) { + for (uint32_t i = 0; i < p_elements; i++) { + const uint16_t *src = (const uint16_t *)&src_vertex_ptr[i * src_vertex_stride]; + float *dst = (float *)&dst_vertex_ptr[i * dst_vertex_stride]; + dst[0] = Math::half_to_float(src[0]); + dst[1] = Math::half_to_float(src[1]); + } + src_offset += sizeof(uint16_t) * 2; + } else { + for (uint32_t i = 0; i < p_elements; i++) { + const float *src = (const float *)&src_vertex_ptr[i * src_vertex_stride]; + float *dst = (float *)&dst_vertex_ptr[i * dst_vertex_stride]; + dst[0] = src[0]; + dst[1] = src[1]; + } + src_offset += sizeof(float) * 2; + } + } else { + if (p_old_format & OLD_ARRAY_COMPRESS_VERTEX) { + for (uint32_t i = 0; i < p_elements; i++) { + const uint16_t *src = (const uint16_t *)&src_vertex_ptr[i * src_vertex_stride]; + float *dst = (float *)&dst_vertex_ptr[i * dst_vertex_stride]; + dst[0] = Math::half_to_float(src[0]); + dst[1] = Math::half_to_float(src[1]); + dst[2] = Math::half_to_float(src[2]); + } + src_offset += sizeof(uint16_t) * 4; //+pad + } else { + for (uint32_t i = 0; i < p_elements; i++) { + const float *src = (const float *)&src_vertex_ptr[i * src_vertex_stride]; + float *dst = (float *)&dst_vertex_ptr[i * dst_vertex_stride]; + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + } + src_offset += sizeof(float) * 3; + } + } + } break; + case OLD_ARRAY_NORMAL: { + if (p_old_format & OLD_ARRAY_COMPRESS_NORMAL) { + const float multiplier = 1.f / 127.f * 1023.0f; + + for (uint32_t i = 0; i < p_elements; i++) { + const int8_t *src = (const int8_t *)&src_vertex_ptr[i * src_vertex_stride + src_offset]; + uint32_t *dst = (uint32_t *)&dst_vertex_ptr[i * dst_vertex_stride + dst_offsets[Mesh::ARRAY_NORMAL]]; + + *dst = 0; + *dst |= CLAMP(int(src[0] * multiplier), 0, 1023); + *dst |= CLAMP(int(src[1] * multiplier), 0, 1023) << 10; + *dst |= CLAMP(int(src[2] * multiplier), 0, 1023) << 20; + } + src_offset += sizeof(uint32_t); + } else { + for (uint32_t i = 0; i < p_elements; i++) { + const float *src = (const float *)&src_vertex_ptr[i * src_vertex_stride + src_offset]; + uint32_t *dst = (uint32_t *)&dst_vertex_ptr[i * dst_vertex_stride + dst_offsets[Mesh::ARRAY_NORMAL]]; + + *dst = 0; + *dst |= CLAMP(int(src[0] * 1023.0), 0, 1023); + *dst |= CLAMP(int(src[1] * 1023.0), 0, 1023) << 10; + *dst |= CLAMP(int(src[2] * 1023.0), 0, 1023) << 20; + } + src_offset += sizeof(float) * 3; + } - src += 4; + } break; + case OLD_ARRAY_TANGENT: { + if (p_old_format & OLD_ARRAY_COMPRESS_TANGENT) { + const float multiplier = 1.f / 127.f * 1023.0f; + + for (uint32_t i = 0; i < p_elements; i++) { + const int8_t *src = (const int8_t *)&src_vertex_ptr[i * src_vertex_stride + src_offset]; + uint32_t *dst = (uint32_t *)&dst_vertex_ptr[i * dst_vertex_stride + dst_offsets[Mesh::ARRAY_TANGENT]]; + + *dst = 0; + *dst |= CLAMP(int(src[0] * multiplier), 0, 1023); + *dst |= CLAMP(int(src[1] * multiplier), 0, 1023) << 10; + *dst |= CLAMP(int(src[2] * multiplier), 0, 1023) << 20; + if (src[3] > 0) { + *dst |= 3 << 30; + } + } + src_offset += sizeof(uint32_t); } else { - for (uint32_t j = 0; j < 8; j++) { - dst[j] = src[j]; + for (uint32_t i = 0; i < p_elements; i++) { + const float *src = (const float *)&src_vertex_ptr[i * src_vertex_stride + src_offset]; + uint32_t *dst = (uint32_t *)&dst_vertex_ptr[i * dst_vertex_stride + dst_offsets[Mesh::ARRAY_TANGENT]]; + + *dst = 0; + *dst |= CLAMP(int(src[0] * 1023.0), 0, 1023); + *dst |= CLAMP(int(src[1] * 1023.0), 0, 1023) << 10; + *dst |= CLAMP(int(src[2] * 1023.0), 0, 1023) << 20; + if (src[3] > 0) { + *dst |= 3 << 30; + } } + src_offset += sizeof(float) * 4; + } - src += 8; + } break; + case OLD_ARRAY_COLOR: { + if (p_old_format & OLD_ARRAY_COMPRESS_COLOR) { + for (uint32_t i = 0; i < p_elements; i++) { + const uint32_t *src = (const uint32_t *)&src_vertex_ptr[i * src_vertex_stride + src_offset]; + uint32_t *dst = (uint32_t *)&dst_attribute_ptr[i * dst_attribute_stride + dst_offsets[Mesh::ARRAY_COLOR]]; + + *dst = *src; + } + src_offset += sizeof(uint32_t); + } else { + for (uint32_t i = 0; i < p_elements; i++) { + const float *src = (const float *)&src_vertex_ptr[i * src_vertex_stride + src_offset]; + uint8_t *dst = (uint8_t *)&dst_attribute_ptr[i * dst_attribute_stride + dst_offsets[Mesh::ARRAY_COLOR]]; + + dst[0] = uint8_t(CLAMP(src[0] * 255.0, 0.0, 255.0)); + dst[1] = uint8_t(CLAMP(src[1] * 255.0, 0.0, 255.0)); + dst[2] = uint8_t(CLAMP(src[2] * 255.0, 0.0, 255.0)); + dst[3] = uint8_t(CLAMP(src[3] * 255.0, 0.0, 255.0)); + } + src_offset += sizeof(float) * 4; } + } break; + case OLD_ARRAY_TEX_UV: { + if (p_old_format & OLD_ARRAY_COMPRESS_TEX_UV) { + for (uint32_t i = 0; i < p_elements; i++) { + const uint16_t *src = (const uint16_t *)&src_vertex_ptr[i * src_vertex_stride + src_offset]; + float *dst = (float *)&dst_attribute_ptr[i * dst_attribute_stride + dst_offsets[Mesh::ARRAY_TEX_UV]]; + + dst[0] = Math::half_to_float(src[0]); + dst[1] = Math::half_to_float(src[1]); + } + src_offset += sizeof(uint16_t) * 2; + } else { + for (uint32_t i = 0; i < p_elements; i++) { + const float *src = (const float *)&src_vertex_ptr[i * src_vertex_stride + src_offset]; + float *dst = (float *)&dst_attribute_ptr[i * dst_attribute_stride + dst_offsets[Mesh::ARRAY_TEX_UV]]; - dst += 8; + dst[0] = src[0]; + dst[1] = src[1]; + } + src_offset += sizeof(float) * 2; + } - if (weight_32) { - const float *src_weights = (const float *)src; - uint16_t *dst_weights = (uint16_t *)dst; + } break; + case OLD_ARRAY_TEX_UV2: { + if (p_old_format & OLD_ARRAY_COMPRESS_TEX_UV2) { + for (uint32_t i = 0; i < p_elements; i++) { + const uint16_t *src = (const uint16_t *)&src_vertex_ptr[i * src_vertex_stride + src_offset]; + float *dst = (float *)&dst_attribute_ptr[i * dst_attribute_stride + dst_offsets[Mesh::ARRAY_TEX_UV2]]; - dst_weights[0] = CLAMP(src_weights[0] * 65535, 0, 65535); //16bits unorm - dst_weights[1] = CLAMP(src_weights[1] * 65535, 0, 65535); - dst_weights[2] = CLAMP(src_weights[2] * 65535, 0, 65535); - dst_weights[3] = CLAMP(src_weights[3] * 65535, 0, 65535); + dst[0] = Math::half_to_float(src[0]); + dst[1] = Math::half_to_float(src[1]); + } + src_offset += sizeof(uint16_t) * 2; + } else { + for (uint32_t i = 0; i < p_elements; i++) { + const float *src = (const float *)&src_vertex_ptr[i * src_vertex_stride + src_offset]; + float *dst = (float *)&dst_attribute_ptr[i * dst_attribute_stride + dst_offsets[Mesh::ARRAY_TEX_UV2]]; + dst[0] = src[0]; + dst[1] = src[1]; + } + src_offset += sizeof(float) * 2; + } + } break; + case OLD_ARRAY_BONES: { + if (p_old_format & OLD_ARRAY_FLAG_USE_16_BIT_BONES) { + for (uint32_t i = 0; i < p_elements; i++) { + const uint16_t *src = (const uint16_t *)&src_vertex_ptr[i * src_vertex_stride + src_offset]; + uint16_t *dst = (uint16_t *)&dst_skin_ptr[i * dst_skin_stride + dst_offsets[Mesh::ARRAY_BONES]]; + + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + } + src_offset += sizeof(uint16_t) * 4; + } else { + for (uint32_t i = 0; i < p_elements; i++) { + const uint8_t *src = (const uint8_t *)&src_vertex_ptr[i * src_vertex_stride + src_offset]; + uint16_t *dst = (uint16_t *)&dst_skin_ptr[i * dst_skin_stride + dst_offsets[Mesh::ARRAY_BONES]]; + + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + } + src_offset += sizeof(uint8_t) * 4; + } + } break; + case OLD_ARRAY_WEIGHTS: { + if (p_old_format & OLD_ARRAY_COMPRESS_WEIGHTS) { + for (uint32_t i = 0; i < p_elements; i++) { + const uint16_t *src = (const uint16_t *)&src_vertex_ptr[i * src_vertex_stride + src_offset]; + uint16_t *dst = (uint16_t *)&dst_skin_ptr[i * dst_skin_stride + dst_offsets[Mesh::ARRAY_WEIGHTS]]; + + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + } + src_offset += sizeof(uint16_t) * 4; } else { - for (uint32_t j = 0; j < 8; j++) { - dst[j] = src[j]; + for (uint32_t i = 0; i < p_elements; i++) { + const float *src = (const float *)&src_vertex_ptr[i * src_vertex_stride + src_offset]; + uint16_t *dst = (uint16_t *)&dst_skin_ptr[i * dst_skin_stride + dst_offsets[Mesh::ARRAY_WEIGHTS]]; + + dst[0] = uint16_t(CLAMP(src[0] * 65535.0, 0, 65535.0)); + dst[1] = uint16_t(CLAMP(src[1] * 65535.0, 0, 65535.0)); + dst[2] = uint16_t(CLAMP(src[2] * 65535.0, 0, 65535.0)); + dst[3] = uint16_t(CLAMP(src[3] * 65535.0, 0, 65535.0)); } + src_offset += sizeof(float) * 4; } + } break; + default: { } } } - - return ret; } -#endif bool ArrayMesh::_set(const StringName &p_name, const Variant &p_value) { String sname = p_name; @@ -771,10 +963,13 @@ bool ArrayMesh::_set(const StringName &p_name, const Variant &p_value) { if (d.has("arrays")) { //oldest format (2.x) ERR_FAIL_COND_V(!d.has("morph_arrays"), false); - add_surface_from_arrays(PrimitiveType(int(d["primitive"])), d["arrays"], d["morph_arrays"]); + Array morph_arrays = d["morph_arrays"]; + for (int i = 0; i < morph_arrays.size(); i++) { + morph_arrays[i] = _convert_old_array(morph_arrays[i]); + } + add_surface_from_arrays(_old_primitives[int(d["primitive"])], _convert_old_array(d["arrays"]), morph_arrays); } else if (d.has("array_data")) { -#if 0 //print_line("array data (old style"); //older format (3.x) Vector<uint8_t> array_data = d["array_data"]; @@ -784,48 +979,76 @@ bool ArrayMesh::_set(const StringName &p_name, const Variant &p_value) { } ERR_FAIL_COND_V(!d.has("format"), false); - uint32_t format = d["format"]; + uint32_t old_format = d["format"]; uint32_t primitive = d["primitive"]; - uint32_t primitive_remap[7] = { - PRIMITIVE_POINTS, - PRIMITIVE_LINES, - PRIMITIVE_LINE_STRIP, - PRIMITIVE_LINES, - PRIMITIVE_TRIANGLES, - PRIMITIVE_TRIANGLE_STRIP, - PRIMITIVE_TRIANGLE_STRIP - }; - - primitive = primitive_remap[primitive]; //compatibility + primitive = _old_primitives[primitive]; //compatibility ERR_FAIL_COND_V(!d.has("vertex_count"), false); int vertex_count = d["vertex_count"]; - array_data = _fix_array_compatibility(array_data, format, vertex_count); + uint32_t new_format = ARRAY_FORMAT_VERTEX; + + if (old_format & OLD_ARRAY_FORMAT_NORMAL) { + new_format |= ARRAY_FORMAT_NORMAL; + } + if (old_format & OLD_ARRAY_FORMAT_TANGENT) { + new_format |= ARRAY_FORMAT_TANGENT; + } + if (old_format & OLD_ARRAY_FORMAT_COLOR) { + new_format |= ARRAY_FORMAT_COLOR; + } + if (old_format & OLD_ARRAY_FORMAT_TEX_UV) { + new_format |= ARRAY_FORMAT_TEX_UV; + } + if (old_format & OLD_ARRAY_FORMAT_TEX_UV2) { + new_format |= ARRAY_FORMAT_TEX_UV2; + } + if (old_format & OLD_ARRAY_FORMAT_BONES) { + new_format |= ARRAY_FORMAT_BONES; + } + if (old_format & OLD_ARRAY_FORMAT_WEIGHTS) { + new_format |= ARRAY_FORMAT_WEIGHTS; + } + if (old_format & OLD_ARRAY_FORMAT_INDEX) { + new_format |= ARRAY_FORMAT_INDEX; + } + if (old_format & OLD_ARRAY_FLAG_USE_2D_VERTICES) { + new_format |= OLD_ARRAY_FLAG_USE_2D_VERTICES; + } + + Vector<uint8_t> vertex_array; + Vector<uint8_t> attribute_array; + Vector<uint8_t> skin_array; + + _fix_array_compatibility(array_data, old_format, new_format, vertex_count, vertex_array, attribute_array, skin_array); int index_count = 0; if (d.has("index_count")) { index_count = d["index_count"]; } - Vector<Vector<uint8_t>> blend_shapes; + Vector<uint8_t> blend_shapes; if (d.has("blend_shape_data")) { Array blend_shape_data = d["blend_shape_data"]; for (int i = 0; i < blend_shape_data.size(); i++) { + Vector<uint8_t> blend_vertex_array; + Vector<uint8_t> blend_attribute_array; + Vector<uint8_t> blend_skin_array; + Vector<uint8_t> shape = blend_shape_data[i]; - shape = _fix_array_compatibility(shape, format, vertex_count); + _fix_array_compatibility(shape, old_format, new_format, vertex_count, blend_vertex_array, blend_attribute_array, blend_skin_array); - blend_shapes.push_back(shape); + blend_shapes.append_array(blend_vertex_array); } } //clear unused flags - print_line("format pre: " + itos(format)); - format &= ~uint32_t((1 << (ARRAY_VERTEX + ARRAY_COMPRESS_BASE)) | (ARRAY_COMPRESS_INDEX << 2) | (ARRAY_COMPRESS_TEX_UV2 << 2)); - print_line("format post: " + itos(format)); + print_line("format pre: " + itos(old_format)); + + print_line("format post: " + itos(new_format)); ERR_FAIL_COND_V(!d.has("aabb"), false); AABB aabb = d["aabb"]; @@ -840,8 +1063,8 @@ bool ArrayMesh::_set(const StringName &p_name, const Variant &p_value) { } } - add_surface(format, PrimitiveType(primitive), array_data, vertex_count, array_index_data, index_count, aabb, blend_shapes, bone_aabb); -#endif + add_surface(new_format, PrimitiveType(primitive), vertex_array, attribute_array, skin_array, vertex_count, array_index_data, index_count, aabb, blend_shapes, bone_aabb); + } else { ERR_FAIL_V(false); } @@ -1112,9 +1335,9 @@ void ArrayMesh::_get_property_list(List<PropertyInfo> *p_list) const { for (int i = 0; i < surfaces.size(); i++) { p_list->push_back(PropertyInfo(Variant::STRING, "surface_" + itos(i + 1) + "/name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); if (surfaces[i].is_2d) { - p_list->push_back(PropertyInfo(Variant::OBJECT, "surface_" + itos(i + 1) + "/material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,CanvasItemMaterial", PROPERTY_USAGE_EDITOR)); + p_list->push_back(PropertyInfo(Variant::OBJECT, "surface_" + itos(i + 1) + "/material", PROPERTY_HINT_RESOURCE_TYPE, "CanvasItemMaterial,ShaderMaterial", PROPERTY_USAGE_EDITOR)); } else { - p_list->push_back(PropertyInfo(Variant::OBJECT, "surface_" + itos(i + 1) + "/material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,StandardMaterial3D", PROPERTY_USAGE_EDITOR)); + p_list->push_back(PropertyInfo(Variant::OBJECT, "surface_" + itos(i + 1) + "/material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_EDITOR)); } } } @@ -1238,6 +1461,22 @@ StringName ArrayMesh::get_blend_shape_name(int p_index) const { return blend_shapes[p_index]; } +void ArrayMesh::set_blend_shape_name(int p_index, const StringName &p_name) { + ERR_FAIL_INDEX(p_index, blend_shapes.size()); + + StringName name = p_name; + int found = blend_shapes.find(name); + if (found != -1 && found != p_index) { + int count = 2; + do { + name = String(p_name) + " " + itos(count); + count++; + } while (blend_shapes.find(name) != -1); + } + + blend_shapes.write[p_index] = name; +} + void ArrayMesh::clear_blend_shapes() { ERR_FAIL_COND_MSG(surfaces.size(), "Can't set shape key count if surfaces are already created."); @@ -1311,9 +1550,21 @@ String ArrayMesh::surface_get_name(int p_idx) const { return surfaces[p_idx].name; } -void ArrayMesh::surface_update_region(int p_surface, int p_offset, const Vector<uint8_t> &p_data) { +void ArrayMesh::surface_update_vertex_region(int p_surface, int p_offset, const Vector<uint8_t> &p_data) { + ERR_FAIL_INDEX(p_surface, surfaces.size()); + RS::get_singleton()->mesh_surface_update_vertex_region(mesh, p_surface, p_offset, p_data); + emit_changed(); +} + +void ArrayMesh::surface_update_attribute_region(int p_surface, int p_offset, const Vector<uint8_t> &p_data) { ERR_FAIL_INDEX(p_surface, surfaces.size()); - RS::get_singleton()->mesh_surface_update_region(mesh, p_surface, p_offset, p_data); + RS::get_singleton()->mesh_surface_update_attribute_region(mesh, p_surface, p_offset, p_data); + emit_changed(); +} + +void ArrayMesh::surface_update_skin_region(int p_surface, int p_offset, const Vector<uint8_t> &p_data) { + ERR_FAIL_INDEX(p_surface, surfaces.size()); + RS::get_singleton()->mesh_surface_update_skin_region(mesh, p_surface, p_offset, p_data); emit_changed(); } @@ -1378,7 +1629,7 @@ void ArrayMesh::regen_normal_maps() { } //dirty hack -bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, int p_index_count, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y, int *&r_cache_data, unsigned int &r_cache_size, bool &r_used_cache); +bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, int p_index_count, const uint8_t *p_cache_data, bool *r_use_cache, uint8_t **r_mesh_cache, int *r_mesh_cache_size, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y) = NULL; struct ArrayMeshLightmapSurface { Ref<Material> material; @@ -1387,29 +1638,29 @@ struct ArrayMeshLightmapSurface { uint32_t format = 0; }; -Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texel_size) { - int *cache_data = nullptr; - unsigned int cache_size = 0; - bool use_cache = false; // Don't use cache - return lightmap_unwrap_cached(cache_data, cache_size, use_cache, p_base_transform, p_texel_size); +Error ArrayMesh::lightmap_unwrap(const Transform3D &p_base_transform, float p_texel_size) { + Vector<uint8_t> null_cache; + return lightmap_unwrap_cached(p_base_transform, p_texel_size, null_cache, null_cache, false); } -Error ArrayMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cache_size, bool &r_used_cache, const Transform &p_base_transform, float p_texel_size) { +Error ArrayMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, float p_texel_size, const Vector<uint8_t> &p_src_cache, Vector<uint8_t> &r_dst_cache, bool p_generate_cache) { ERR_FAIL_COND_V(!array_mesh_lightmap_unwrap_callback, ERR_UNCONFIGURED); ERR_FAIL_COND_V_MSG(blend_shapes.size() != 0, ERR_UNAVAILABLE, "Can't unwrap mesh with blend shapes."); - Vector<float> vertices; - Vector<float> normals; - Vector<int> indices; - Vector<float> uv; - Vector<Pair<int, int>> uv_indices; + LocalVector<float> vertices; + LocalVector<float> normals; + LocalVector<int> indices; + LocalVector<float> uv; + LocalVector<Pair<int, int>> uv_indices; Vector<ArrayMeshLightmapSurface> lightmap_surfaces; // Keep only the scale - Transform transform = p_base_transform; - transform.origin = Vector3(); - transform.looking_at(Vector3(1, 0, 0), Vector3(0, 1, 0)); + Basis basis = p_base_transform.get_basis(); + Vector3 scale = Vector3(basis.get_axis(0).length(), basis.get_axis(1).length(), basis.get_axis(2).length()); + + Transform3D transform; + transform.scale(scale); Basis normal_basis = transform.basis.inverse().transposed(); @@ -1423,14 +1674,12 @@ Error ArrayMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cach Array arrays = surface_get_arrays(i); s.material = surface_get_material(i); - SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices); + SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices, &s.format); - Vector<Vector3> rvertices = arrays[Mesh::ARRAY_VERTEX]; + PackedVector3Array rvertices = arrays[Mesh::ARRAY_VERTEX]; int vc = rvertices.size(); - const Vector3 *r = rvertices.ptr(); - Vector<Vector3> rnormals = arrays[Mesh::ARRAY_NORMAL]; - const Vector3 *rn = rnormals.ptr(); + PackedVector3Array rnormals = arrays[Mesh::ARRAY_NORMAL]; int vertex_ofs = vertices.size() / 3; @@ -1439,24 +1688,29 @@ Error ArrayMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cach uv_indices.resize(vertex_ofs + vc); for (int j = 0; j < vc; j++) { - Vector3 v = transform.xform(r[j]); - Vector3 n = normal_basis.xform(rn[j]).normalized(); - - vertices.write[(j + vertex_ofs) * 3 + 0] = v.x; - vertices.write[(j + vertex_ofs) * 3 + 1] = v.y; - vertices.write[(j + vertex_ofs) * 3 + 2] = v.z; - normals.write[(j + vertex_ofs) * 3 + 0] = n.x; - normals.write[(j + vertex_ofs) * 3 + 1] = n.y; - normals.write[(j + vertex_ofs) * 3 + 2] = n.z; - uv_indices.write[j + vertex_ofs] = Pair<int, int>(i, j); + Vector3 v = transform.xform(rvertices[j]); + Vector3 n = normal_basis.xform(rnormals[j]).normalized(); + + vertices[(j + vertex_ofs) * 3 + 0] = v.x; + vertices[(j + vertex_ofs) * 3 + 1] = v.y; + vertices[(j + vertex_ofs) * 3 + 2] = v.z; + normals[(j + vertex_ofs) * 3 + 0] = n.x; + normals[(j + vertex_ofs) * 3 + 1] = n.y; + normals[(j + vertex_ofs) * 3 + 2] = n.z; + uv_indices[j + vertex_ofs] = Pair<int, int>(i, j); } - Vector<int> rindices = arrays[Mesh::ARRAY_INDEX]; + PackedInt32Array rindices = arrays[Mesh::ARRAY_INDEX]; int ic = rindices.size(); + float eps = 1.19209290e-7F; // Taken from xatlas.h if (ic == 0) { for (int j = 0; j < vc / 3; j++) { - if (Face3(r[j * 3 + 0], r[j * 3 + 1], r[j * 3 + 2]).is_degenerate()) { + Vector3 p0 = transform.xform(rvertices[j * 3 + 0]); + Vector3 p1 = transform.xform(rvertices[j * 3 + 1]); + Vector3 p2 = transform.xform(rvertices[j * 3 + 2]); + + if ((p0 - p1).length_squared() < eps || (p1 - p2).length_squared() < eps || (p2 - p0).length_squared() < eps) { continue; } @@ -1466,15 +1720,18 @@ Error ArrayMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cach } } else { - const int *ri = rindices.ptr(); - for (int j = 0; j < ic / 3; j++) { - if (Face3(r[ri[j * 3 + 0]], r[ri[j * 3 + 1]], r[ri[j * 3 + 2]]).is_degenerate()) { + Vector3 p0 = transform.xform(rvertices[rindices[j * 3 + 0]]); + Vector3 p1 = transform.xform(rvertices[rindices[j * 3 + 1]]); + Vector3 p2 = transform.xform(rvertices[rindices[j * 3 + 2]]); + + if ((p0 - p1).length_squared() < eps || (p1 - p2).length_squared() < eps || (p2 - p0).length_squared() < eps) { continue; } - indices.push_back(vertex_ofs + ri[j * 3 + 0]); - indices.push_back(vertex_ofs + ri[j * 3 + 1]); - indices.push_back(vertex_ofs + ri[j * 3 + 2]); + + indices.push_back(vertex_ofs + rindices[j * 3 + 0]); + indices.push_back(vertex_ofs + rindices[j * 3 + 1]); + indices.push_back(vertex_ofs + rindices[j * 3 + 2]); } } @@ -1483,6 +1740,9 @@ Error ArrayMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cach //unwrap + bool use_cache = p_generate_cache; // Used to request cache generation and to know if cache was used + uint8_t *gen_cache; + int gen_cache_size; float *gen_uvs; int *gen_vertices; int *gen_indices; @@ -1491,32 +1751,32 @@ Error ArrayMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cach int size_x; int size_y; - bool ok = array_mesh_lightmap_unwrap_callback(p_texel_size, vertices.ptr(), normals.ptr(), vertices.size() / 3, indices.ptr(), indices.size(), &gen_uvs, &gen_vertices, &gen_vertex_count, &gen_indices, &gen_index_count, &size_x, &size_y, r_cache_data, r_cache_size, r_used_cache); + bool ok = array_mesh_lightmap_unwrap_callback(p_texel_size, vertices.ptr(), normals.ptr(), vertices.size() / 3, indices.ptr(), indices.size(), p_src_cache.ptr(), &use_cache, &gen_cache, &gen_cache_size, &gen_uvs, &gen_vertices, &gen_vertex_count, &gen_indices, &gen_index_count, &size_x, &size_y); if (!ok) { return ERR_CANT_CREATE; } - //remove surfaces clear_surfaces(); //create surfacetools for each surface.. - Vector<Ref<SurfaceTool>> surfaces_tools; + LocalVector<Ref<SurfaceTool>> surfaces_tools; for (int i = 0; i < lightmap_surfaces.size(); i++) { Ref<SurfaceTool> st; - st.instance(); + st.instantiate(); st->begin(Mesh::PRIMITIVE_TRIANGLES); st->set_material(lightmap_surfaces[i].material); surfaces_tools.push_back(st); //stay there } print_verbose("Mesh: Gen indices: " + itos(gen_index_count)); + //go through all indices for (int i = 0; i < gen_index_count; i += 3) { - ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 0]], uv_indices.size(), ERR_BUG); - ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 1]], uv_indices.size(), ERR_BUG); - ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 2]], uv_indices.size(), ERR_BUG); + ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 0]], (int)uv_indices.size(), ERR_BUG); + ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 1]], (int)uv_indices.size(), ERR_BUG); + ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 2]], (int)uv_indices.size(), ERR_BUG); ERR_FAIL_COND_V(uv_indices[gen_vertices[gen_indices[i + 0]]].first != uv_indices[gen_vertices[gen_indices[i + 1]]].first || uv_indices[gen_vertices[gen_indices[i + 0]]].first != uv_indices[gen_vertices[gen_indices[i + 2]]].first, ERR_BUG); @@ -1526,48 +1786,53 @@ Error ArrayMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cach SurfaceTool::Vertex v = lightmap_surfaces[surface].vertices[uv_indices[gen_vertices[gen_indices[i + j]]].second]; if (lightmap_surfaces[surface].format & ARRAY_FORMAT_COLOR) { - surfaces_tools.write[surface]->set_color(v.color); + surfaces_tools[surface]->set_color(v.color); } if (lightmap_surfaces[surface].format & ARRAY_FORMAT_TEX_UV) { - surfaces_tools.write[surface]->set_uv(v.uv); + surfaces_tools[surface]->set_uv(v.uv); } if (lightmap_surfaces[surface].format & ARRAY_FORMAT_NORMAL) { - surfaces_tools.write[surface]->set_normal(v.normal); + surfaces_tools[surface]->set_normal(v.normal); } if (lightmap_surfaces[surface].format & ARRAY_FORMAT_TANGENT) { Plane t; t.normal = v.tangent; t.d = v.binormal.dot(v.normal.cross(v.tangent)) < 0 ? -1 : 1; - surfaces_tools.write[surface]->set_tangent(t); + surfaces_tools[surface]->set_tangent(t); } if (lightmap_surfaces[surface].format & ARRAY_FORMAT_BONES) { - surfaces_tools.write[surface]->set_bones(v.bones); + surfaces_tools[surface]->set_bones(v.bones); } if (lightmap_surfaces[surface].format & ARRAY_FORMAT_WEIGHTS) { - surfaces_tools.write[surface]->set_weights(v.weights); + surfaces_tools[surface]->set_weights(v.weights); } Vector2 uv2(gen_uvs[gen_indices[i + j] * 2 + 0], gen_uvs[gen_indices[i + j] * 2 + 1]); - surfaces_tools.write[surface]->set_uv2(uv2); + surfaces_tools[surface]->set_uv2(uv2); - surfaces_tools.write[surface]->add_vertex(v.vertex); + surfaces_tools[surface]->add_vertex(v.vertex); } } //generate surfaces - - for (int i = 0; i < surfaces_tools.size(); i++) { - surfaces_tools.write[i]->index(); - surfaces_tools.write[i]->commit(Ref<ArrayMesh>((ArrayMesh *)this), lightmap_surfaces[i].format); + for (unsigned int i = 0; i < surfaces_tools.size(); i++) { + surfaces_tools[i]->index(); + surfaces_tools[i]->commit(Ref<ArrayMesh>((ArrayMesh *)this), lightmap_surfaces[i].format); } set_lightmap_size_hint(Size2(size_x, size_y)); - if (!r_used_cache) { - //free stuff - ::free(gen_vertices); - ::free(gen_indices); - ::free(gen_uvs); + if (gen_cache_size > 0) { + r_dst_cache.resize(gen_cache_size); + memcpy(r_dst_cache.ptrw(), gen_cache, gen_cache_size); + memfree(gen_cache); + } + + if (!use_cache) { + // Cache was not used, free the buffers + memfree(gen_vertices); + memfree(gen_indices); + memfree(gen_uvs); } return OK; @@ -1590,13 +1855,16 @@ void ArrayMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("add_blend_shape", "name"), &ArrayMesh::add_blend_shape); ClassDB::bind_method(D_METHOD("get_blend_shape_count"), &ArrayMesh::get_blend_shape_count); ClassDB::bind_method(D_METHOD("get_blend_shape_name", "index"), &ArrayMesh::get_blend_shape_name); + ClassDB::bind_method(D_METHOD("set_blend_shape_name", "index", "name"), &ArrayMesh::set_blend_shape_name); ClassDB::bind_method(D_METHOD("clear_blend_shapes"), &ArrayMesh::clear_blend_shapes); ClassDB::bind_method(D_METHOD("set_blend_shape_mode", "mode"), &ArrayMesh::set_blend_shape_mode); ClassDB::bind_method(D_METHOD("get_blend_shape_mode"), &ArrayMesh::get_blend_shape_mode); ClassDB::bind_method(D_METHOD("add_surface_from_arrays", "primitive", "arrays", "blend_shapes", "lods", "compress_flags"), &ArrayMesh::add_surface_from_arrays, DEFVAL(Array()), DEFVAL(Dictionary()), DEFVAL(0)); ClassDB::bind_method(D_METHOD("clear_surfaces"), &ArrayMesh::clear_surfaces); - ClassDB::bind_method(D_METHOD("surface_update_region", "surf_idx", "offset", "data"), &ArrayMesh::surface_update_region); + ClassDB::bind_method(D_METHOD("surface_update_vertex_region", "surf_idx", "offset", "data"), &ArrayMesh::surface_update_vertex_region); + ClassDB::bind_method(D_METHOD("surface_update_attribute_region", "surf_idx", "offset", "data"), &ArrayMesh::surface_update_attribute_region); + ClassDB::bind_method(D_METHOD("surface_update_skin_region", "surf_idx", "offset", "data"), &ArrayMesh::surface_update_skin_region); ClassDB::bind_method(D_METHOD("surface_get_array_len", "surf_idx"), &ArrayMesh::surface_get_array_len); ClassDB::bind_method(D_METHOD("surface_get_array_index_len", "surf_idx"), &ArrayMesh::surface_get_array_index_len); ClassDB::bind_method(D_METHOD("surface_get_format", "surf_idx"), &ArrayMesh::surface_get_format); @@ -1605,7 +1873,7 @@ void ArrayMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("surface_set_name", "surf_idx", "name"), &ArrayMesh::surface_set_name); ClassDB::bind_method(D_METHOD("surface_get_name", "surf_idx"), &ArrayMesh::surface_get_name); ClassDB::bind_method(D_METHOD("create_trimesh_shape"), &ArrayMesh::create_trimesh_shape); - ClassDB::bind_method(D_METHOD("create_convex_shape"), &ArrayMesh::create_convex_shape); + ClassDB::bind_method(D_METHOD("create_convex_shape", "clean", "simplify"), &ArrayMesh::create_convex_shape, DEFVAL(true), DEFVAL(false)); ClassDB::bind_method(D_METHOD("create_outline", "margin"), &ArrayMesh::create_outline); ClassDB::bind_method(D_METHOD("regen_normal_maps"), &ArrayMesh::regen_normal_maps); ClassDB::set_method_flags(get_class_static(), _scs_create("regen_normal_maps"), METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR); diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index 2ce519e644..27b0eb098b 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -141,6 +141,7 @@ public: virtual Ref<Material> surface_get_material(int p_idx) const = 0; virtual int get_blend_shape_count() const = 0; virtual StringName get_blend_shape_name(int p_index) const = 0; + virtual void set_blend_shape_name(int p_index, const StringName &p_name) = 0; Vector<Face3> get_faces() const; Ref<TriangleMesh> generate_triangle_mesh() const; @@ -148,7 +149,7 @@ public: void generate_debug_mesh_indices(Vector<Vector3> &r_points); Ref<Shape3D> create_trimesh_shape() const; - Ref<Shape3D> create_convex_shape() const; + Ref<Shape3D> create_convex_shape(bool p_clean = true, bool p_simplify = false) const; Ref<Mesh> create_outline(float p_margin) const; @@ -158,11 +159,14 @@ public: Size2i get_lightmap_size_hint() const; void clear_cache() const; - typedef Vector<Vector<Face3>> (*ConvexDecompositionFunc)(const Vector<Face3> &); + typedef Vector<Vector<Face3>> (*ConvexDecompositionFunc)(const Vector<Face3> &p_faces, int p_max_convex_hulls); static ConvexDecompositionFunc convex_composition_function; - Vector<Ref<Shape3D>> convex_decompose() const; + Vector<Ref<Shape3D>> convex_decompose(int p_max_convex_hulls = -1) const; + + virtual int get_builtin_bind_pose_count() const; + virtual Transform3D get_builtin_bind_pose(int p_index) const; Mesh(); }; @@ -223,12 +227,15 @@ public: void add_blend_shape(const StringName &p_name); int get_blend_shape_count() const override; StringName get_blend_shape_name(int p_index) const override; + void set_blend_shape_name(int p_index, const StringName &p_name) override; void clear_blend_shapes(); void set_blend_shape_mode(BlendShapeMode p_mode); BlendShapeMode get_blend_shape_mode() const; - void surface_update_region(int p_surface, int p_offset, const Vector<uint8_t> &p_data); + void surface_update_vertex_region(int p_surface, int p_offset, const Vector<uint8_t> &p_data); + void surface_update_attribute_region(int p_surface, int p_offset, const Vector<uint8_t> &p_data); + void surface_update_skin_region(int p_surface, int p_offset, const Vector<uint8_t> &p_data); int get_surface_count() const override; @@ -257,8 +264,8 @@ public: void regen_normal_maps(); - Error lightmap_unwrap(const Transform &p_base_transform = Transform(), float p_texel_size = 0.05); - Error lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cache_size, bool &r_used_cache, const Transform &p_base_transform = Transform(), float p_texel_size = 0.05); + Error lightmap_unwrap(const Transform3D &p_base_transform = Transform3D(), float p_texel_size = 0.05); + Error lightmap_unwrap_cached(const Transform3D &p_base_transform, float p_texel_size, const Vector<uint8_t> &p_src_cache, Vector<uint8_t> &r_dst_cache, bool p_generate_cache = true); virtual void reload_from_file() override; diff --git a/scene/resources/mesh_data_tool.h b/scene/resources/mesh_data_tool.h index f5c8f11437..b0ebfba7ee 100644 --- a/scene/resources/mesh_data_tool.h +++ b/scene/resources/mesh_data_tool.h @@ -33,8 +33,8 @@ #include "scene/resources/mesh.h" -class MeshDataTool : public Reference { - GDCLASS(MeshDataTool, Reference); +class MeshDataTool : public RefCounted { + GDCLASS(MeshDataTool, RefCounted); int format = 0; struct Vertex { diff --git a/scene/resources/mesh_library.cpp b/scene/resources/mesh_library.cpp index ad90481fbd..33c9ca6d1e 100644 --- a/scene/resources/mesh_library.cpp +++ b/scene/resources/mesh_library.cpp @@ -97,10 +97,10 @@ void MeshLibrary::_get_property_list(List<PropertyInfo> *p_list) const { String name = "item/" + itos(E->key()) + "/"; p_list->push_back(PropertyInfo(Variant::STRING, name + "name")); p_list->push_back(PropertyInfo(Variant::OBJECT, name + "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh")); - p_list->push_back(PropertyInfo(Variant::TRANSFORM, name + "mesh_transform")); + p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, name + "mesh_transform")); p_list->push_back(PropertyInfo(Variant::ARRAY, name + "shapes")); p_list->push_back(PropertyInfo(Variant::OBJECT, name + "navmesh", PROPERTY_HINT_RESOURCE_TYPE, "NavigationMesh")); - p_list->push_back(PropertyInfo(Variant::TRANSFORM, name + "navmesh_transform")); + p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, name + "navmesh_transform")); p_list->push_back(PropertyInfo(Variant::OBJECT, name + "preview", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_HELPER)); } } @@ -145,7 +145,7 @@ void MeshLibrary::set_item_navmesh(int p_item, const Ref<NavigationMesh> &p_navm notify_property_list_changed(); } -void MeshLibrary::set_item_navmesh_transform(int p_item, const Transform &p_transform) { +void MeshLibrary::set_item_navmesh_transform(int p_item, const Transform3D &p_transform) { ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); item_map[p_item].navmesh_transform = p_transform; notify_change_to_owners(); @@ -180,8 +180,8 @@ Ref<NavigationMesh> MeshLibrary::get_item_navmesh(int p_item) const { return item_map[p_item].navmesh; } -Transform MeshLibrary::get_item_navmesh_transform(int p_item) const { - ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Transform(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); +Transform3D MeshLibrary::get_item_navmesh_transform(int p_item) const { + ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Transform3D(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); return item_map[p_item].navmesh_transform; } diff --git a/scene/resources/mesh_library.h b/scene/resources/mesh_library.h index 1da624c275..1e8a6bf3ff 100644 --- a/scene/resources/mesh_library.h +++ b/scene/resources/mesh_library.h @@ -44,14 +44,14 @@ class MeshLibrary : public Resource { public: struct ShapeData { Ref<Shape3D> shape; - Transform local_transform; + Transform3D local_transform; }; struct Item { String name; Ref<Mesh> mesh; Vector<ShapeData> shapes; Ref<Texture2D> preview; - Transform navmesh_transform; + Transform3D navmesh_transform; Ref<NavigationMesh> navmesh; }; @@ -73,13 +73,13 @@ public: void set_item_name(int p_item, const String &p_name); void set_item_mesh(int p_item, const Ref<Mesh> &p_mesh); void set_item_navmesh(int p_item, const Ref<NavigationMesh> &p_navmesh); - void set_item_navmesh_transform(int p_item, const Transform &p_transform); + void set_item_navmesh_transform(int p_item, const Transform3D &p_transform); void set_item_shapes(int p_item, const Vector<ShapeData> &p_shapes); void set_item_preview(int p_item, const Ref<Texture2D> &p_preview); String get_item_name(int p_item) const; Ref<Mesh> get_item_mesh(int p_item) const; Ref<NavigationMesh> get_item_navmesh(int p_item) const; - Transform get_item_navmesh_transform(int p_item) const; + Transform3D get_item_navmesh_transform(int p_item) const; Vector<ShapeData> get_item_shapes(int p_item) const; Ref<Texture2D> get_item_preview(int p_item) const; diff --git a/scene/resources/multimesh.cpp b/scene/resources/multimesh.cpp index 4991887eb3..8894f0bb11 100644 --- a/scene/resources/multimesh.cpp +++ b/scene/resources/multimesh.cpp @@ -50,7 +50,7 @@ void MultiMesh::_set_transform_array(const Vector<Vector3> &p_array) { const Vector3 *r = xforms.ptr(); for (int i = 0; i < len / 4; i++) { - Transform t; + Transform3D t; t.basis[0] = r[i * 4 + 0]; t.basis[1] = r[i * 4 + 1]; t.basis[2] = r[i * 4 + 2]; @@ -75,7 +75,7 @@ Vector<Vector3> MultiMesh::_get_transform_array() const { Vector3 *w = xforms.ptrw(); for (int i = 0; i < instance_count; i++) { - Transform t = get_instance_transform(i); + Transform3D t = get_instance_transform(i); w[i * 4 + 0] = t.basis[0]; w[i * 4 + 1] = t.basis[1]; w[i * 4 + 2] = t.basis[2]; @@ -236,7 +236,7 @@ int MultiMesh::get_visible_instance_count() const { return visible_instance_count; } -void MultiMesh::set_instance_transform(int p_instance, const Transform &p_transform) { +void MultiMesh::set_instance_transform(int p_instance, const Transform3D &p_transform) { RenderingServer::get_singleton()->multimesh_instance_set_transform(multimesh, p_instance, p_transform); } @@ -244,7 +244,7 @@ void MultiMesh::set_instance_transform_2d(int p_instance, const Transform2D &p_t RenderingServer::get_singleton()->multimesh_instance_set_transform_2d(multimesh, p_instance, p_transform); } -Transform MultiMesh::get_instance_transform(int p_instance) const { +Transform3D MultiMesh::get_instance_transform(int p_instance) const { return RenderingServer::get_singleton()->multimesh_instance_get_transform(multimesh, p_instance); } @@ -349,10 +349,10 @@ void MultiMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_custom_data_array"), &MultiMesh::_set_custom_data_array); ClassDB::bind_method(D_METHOD("_get_custom_data_array"), &MultiMesh::_get_custom_data_array); - ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "transform_array", PROPERTY_HINT_NONE, "", 0), "_set_transform_array", "_get_transform_array"); - ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "transform_2d_array", PROPERTY_HINT_NONE, "", 0), "_set_transform_2d_array", "_get_transform_2d_array"); - ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "color_array", PROPERTY_HINT_NONE, "", 0), "_set_color_array", "_get_color_array"); - ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "custom_data_array", PROPERTY_HINT_NONE, "", 0), "_set_custom_data_array", "_get_custom_data_array"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "transform_array", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "_set_transform_array", "_get_transform_array"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "transform_2d_array", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "_set_transform_2d_array", "_get_transform_2d_array"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "color_array", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "_set_color_array", "_get_color_array"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "custom_data_array", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "_set_custom_data_array", "_get_custom_data_array"); #endif BIND_ENUM_CONSTANT(TRANSFORM_2D); diff --git a/scene/resources/multimesh.h b/scene/resources/multimesh.h index ca5c42d47a..2fe0927e6f 100644 --- a/scene/resources/multimesh.h +++ b/scene/resources/multimesh.h @@ -92,9 +92,9 @@ public: void set_visible_instance_count(int p_count); int get_visible_instance_count() const; - void set_instance_transform(int p_instance, const Transform &p_transform); + void set_instance_transform(int p_instance, const Transform3D &p_transform); void set_instance_transform_2d(int p_instance, const Transform2D &p_transform); - Transform get_instance_transform(int p_instance) const; + Transform3D get_instance_transform(int p_instance) const; Transform2D get_instance_transform_2d(int p_instance) const; void set_instance_color(int p_instance, const Color &p_color); diff --git a/scene/resources/navigation_mesh.cpp b/scene/resources/navigation_mesh.cpp index 8c12f59a00..d2be2bdba1 100644 --- a/scene/resources/navigation_mesh.cpp +++ b/scene/resources/navigation_mesh.cpp @@ -92,6 +92,7 @@ uint32_t NavigationMesh::get_collision_mask() const { } void NavigationMesh::set_collision_mask_bit(int p_bit, bool p_value) { + ERR_FAIL_INDEX_MSG(p_bit, 32, "Collision mask bit must be between 0 and 31 inclusive."); uint32_t mask = get_collision_mask(); if (p_value) { mask |= 1 << p_bit; @@ -102,6 +103,7 @@ void NavigationMesh::set_collision_mask_bit(int p_bit, bool p_value) { } bool NavigationMesh::get_collision_mask_bit(int p_bit) const { + ERR_FAIL_INDEX_V_MSG(p_bit, 32, false, "Collision mask bit must be between 0 and 31 inclusive."); return get_collision_mask() & (1 << p_bit); } @@ -499,14 +501,14 @@ void NavigationMesh::_bind_methods() { void NavigationMesh::_validate_property(PropertyInfo &property) const { if (property.name == "geometry/collision_mask") { if (parsed_geometry_type == PARSED_GEOMETRY_MESH_INSTANCES) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; return; } } if (property.name == "geometry/source_group_name") { if (source_geometry_mode == SOURCE_GEOMETRY_NAVMESH_CHILDREN) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; return; } } diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index ab8a4b7934..9bb2a4ddb8 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -41,11 +41,11 @@ #define PACKED_SCENE_VERSION 2 -bool SceneState::can_instance() const { +bool SceneState::can_instantiate() const { return nodes.size() > 0; } -Node *SceneState::instance(GenEditState p_edit_state) const { +Node *SceneState::instantiate(GenEditState p_edit_state) const { // nodes where instancing failed (because something is missing) List<Node *> stray_instances; @@ -109,7 +109,7 @@ Node *SceneState::instance(GenEditState p_edit_state) const { //scene inheritance on root node Ref<PackedScene> sdata = props[base_scene_idx]; ERR_FAIL_COND_V(!sdata.is_valid(), nullptr); - node = sdata->instance(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); //only main gets main edit state + node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); //only main gets main edit state ERR_FAIL_COND_V(!node, nullptr); if (p_edit_state != GEN_EDIT_STATE_DISABLED) { node->set_scene_inherited_state(sdata->get_state()); @@ -122,7 +122,7 @@ Node *SceneState::instance(GenEditState p_edit_state) const { if (disable_placeholders) { Ref<PackedScene> sdata = ResourceLoader::load(path, "PackedScene"); ERR_FAIL_COND_V(!sdata.is_valid(), nullptr); - node = sdata->instance(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); + node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); ERR_FAIL_COND_V(!node, nullptr); } else { InstancePlaceholder *ip = memnew(InstancePlaceholder); @@ -133,7 +133,7 @@ Node *SceneState::instance(GenEditState p_edit_state) const { } else { Ref<PackedScene> sdata = props[n.instance & FLAG_MASK]; ERR_FAIL_COND_V(!sdata.is_valid(), nullptr); - node = sdata->instance(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); + node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); ERR_FAIL_COND_V(!node, nullptr); } @@ -152,7 +152,7 @@ Node *SceneState::instance(GenEditState p_edit_state) const { if (ClassDB::is_class_enabled(snames[n.type])) { //node belongs to this scene and must be created - obj = ClassDB::instance(snames[n.type]); + obj = ClassDB::instantiate(snames[n.type]); } if (!Object::cast_to<Node>(obj)) { @@ -180,7 +180,7 @@ Node *SceneState::instance(GenEditState p_edit_state) const { } if (node) { - // may not have found the node (part of instanced scene and removed) + // may not have found the node (part of instantiated scene and removed) // if found all is good, otherwise ignore //properties @@ -266,7 +266,7 @@ Node *SceneState::instance(GenEditState p_edit_state) const { parent->move_child(node, n.index); } } else { - //it may be possible that an instanced scene has changed + //it may be possible that an instantiated scene has changed //and the node has nowhere to go anymore stray_instances.push_back(node); //can't be added, go to stray list } @@ -368,7 +368,7 @@ static int _vm_get_variant(const Variant &p_variant, HashMap<Variant, int, Varia Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map<StringName, int> &name_map, HashMap<Variant, int, VariantHasher, VariantComparator> &variant_map, Map<Node *, int> &node_map, Map<Node *, int> &nodepath_map) { // this function handles all the work related to properly packing scenes, be it - // instanced or inherited. + // instantiated or inherited. // given the complexity of this process, an attempt will be made to properly // document it. if you fail to understand something, please ask! @@ -377,7 +377,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map return OK; } - // save the child instanced scenes that are chosen as editable, so they can be restored + // save the child instantiated scenes that are chosen as editable, so they can be restored // upon load back if (p_node != p_owner && p_node->get_filename() != String() && p_owner->is_editable_instance(p_node)) { editable_instances.push_back(p_owner->get_path_to(p_node)); @@ -386,7 +386,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map NodeData nd; nd.name = _nm_get_string(p_node->get_name(), name_map); - nd.instance = -1; //not instanced by default + nd.instance = -1; //not instantiated by default //really convoluted condition, but it basically checks that index is only saved when part of an inherited scene OR the node parent is from the edited scene if (p_owner->get_scene_inherited_state().is_null() && (p_node == p_owner || (p_node->get_owner() == p_owner && (p_node->get_parent() == p_owner || p_node->get_parent()->get_owner() == p_owner)))) { @@ -396,18 +396,18 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map //This (hopefully) happens if the node is a scene root, so its index is irrelevant. nd.index = -1; } else { - //part of an inherited scene, or parent is from an instanced scene + //part of an inherited scene, or parent is from an instantiated scene nd.index = p_node->get_index(); } - // if this node is part of an instanced scene or sub-instanced scene + // if this node is part of an instantiated scene or sub-instantiated scene // we need to get the corresponding instance states. // with the instance states, we can query for identical properties/groups // and only save what has changed List<PackState> pack_state_stack; - bool instanced_by_owner = true; + bool instantiated_by_owner = true; { Node *n = p_node; @@ -423,11 +423,11 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map ps.node = node; ps.state = state; pack_state_stack.push_back(ps); - instanced_by_owner = false; + instantiated_by_owner = false; } } - if (p_node->get_filename() != String() && p_node->get_owner() == p_owner && instanced_by_owner) { + if (p_node->get_filename() != String() && p_node->get_owner() == p_owner && instantiated_by_owner) { if (p_node->get_scene_instance_load_placeholder()) { //it's a placeholder, use the placeholder path nd.instance = _vm_get_variant(p_node->get_filename(), variant_map); @@ -471,7 +471,9 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map StringName type = p_node->get_class(); Ref<Script> script = p_node->get_script(); - if (script.is_valid()) { + if (Engine::get_singleton()->is_editor_hint() && script.is_valid()) { + // Should be called in the editor only and not at runtime, + // otherwise it can cause problems because of missing instance state support. script->update_exports(); } @@ -500,8 +502,8 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map // } if (pack_state_stack.size()) { - // we are on part of an instanced subscene - // or part of instanced scene. + // we are on part of an instantiated subscene + // or part of instantiated scene. // only save what has been changed // only save changed properties in instance @@ -571,7 +573,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map } /* if (instance_state_node>=0 && instance_state->is_node_in_group(instance_state_node,gi.name)) - continue; //group was instanced, don't add here + continue; //group was instantiated, don't add here */ bool skip = false; @@ -594,7 +596,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map // save the right owner // for the saved scene root this is -1 // for nodes of the saved scene this is 0 - // for nodes of instanced scenes this is >0 + // for nodes of instantiated scenes this is >0 if (p_node == p_owner) { //saved scene root @@ -612,20 +614,20 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map //this node is not part of an instancing process, so save the type nd.type = _nm_get_string(p_node->get_class(), name_map); } else { - // this node is part of an instanced process, so do not save the type. - // instead, save that it was instanced + // this node is part of an instantiated process, so do not save the type. + // instead, save that it was instantiated nd.type = TYPE_INSTANCED; } // determine whether to save this node or not - // if this node is part of an instanced sub-scene, we can skip storing it if basically + // if this node is part of an instantiated sub-scene, we can skip storing it if basically // no properties changed and no groups were added to it. // below condition is true for all nodes of the scene being saved, and ones in subscenes // that hold changes bool save_node = nd.properties.size() || nd.groups.size(); // some local properties or groups exist save_node = save_node || p_node == p_owner; // owner is always saved - save_node = save_node || (p_node->get_owner() == p_owner && instanced_by_owner); //part of scene and not instanced + save_node = save_node || (p_node->get_owner() == p_owner && instantiated_by_owner); //part of scene and not instantiated int idx = nodes.size(); int parent_node = NO_PARENT_SAVED; @@ -893,6 +895,13 @@ Error SceneState::pack(Node *p_scene) { node_paths.write[E->get()] = scene->get_path_to(E->key()); } + if (Engine::get_singleton()->is_editor_hint()) { + // Build node path cache + for (Map<Node *, int>::Element *E = node_map.front(); E; E = E->next()) { + node_path_cache[scene->get_path_to(E->key())] = E->get(); + } + } + return OK; } @@ -927,10 +936,12 @@ Ref<SceneState> SceneState::_get_base_scene_state() const { } int SceneState::find_node_by_path(const NodePath &p_node) const { + ERR_FAIL_COND_V_MSG(node_path_cache.size() == 0, -1, "This operation requires the node cache to have been built."); + if (!node_path_cache.has(p_node)) { if (_get_base_scene_state().is_valid()) { int idx = _get_base_scene_state()->find_node_by_path(p_node); - if (idx >= 0) { + if (idx != -1) { int rkey = _find_base_scene_node_remap_key(idx); if (rkey == -1) { rkey = nodes.size() + base_scene_node_remap.size(); @@ -946,7 +957,7 @@ int SceneState::find_node_by_path(const NodePath &p_node) const { if (_get_base_scene_state().is_valid() && !base_scene_node_remap.has(nid)) { //for nodes that _do_ exist in current scene, still try to look for - //the node in the instanced scene, as a property may be missing + //the node in the instantiated scene, as a property may be missing //from the local one int idx = _get_base_scene_state()->find_node_by_path(p_node); if (idx != -1) { @@ -1614,16 +1625,16 @@ void PackedScene::clear() { state->clear(); } -bool PackedScene::can_instance() const { - return state->can_instance(); +bool PackedScene::can_instantiate() const { + return state->can_instantiate(); } -Node *PackedScene::instance(GenEditState p_edit_state) const { +Node *PackedScene::instantiate(GenEditState p_edit_state) const { #ifndef TOOLS_ENABLED ERR_FAIL_COND_V_MSG(p_edit_state != GEN_EDIT_STATE_DISABLED, nullptr, "Edit state is only for editors, does not work without tools compiled."); #endif - Node *s = state->instance((SceneState::GenEditState)p_edit_state); + Node *s = state->instantiate((SceneState::GenEditState)p_edit_state); if (!s) { return nullptr; } @@ -1671,8 +1682,8 @@ void PackedScene::reset_state() { } void PackedScene::_bind_methods() { ClassDB::bind_method(D_METHOD("pack", "path"), &PackedScene::pack); - ClassDB::bind_method(D_METHOD("instance", "edit_state"), &PackedScene::instance, DEFVAL(GEN_EDIT_STATE_DISABLED)); - ClassDB::bind_method(D_METHOD("can_instance"), &PackedScene::can_instance); + ClassDB::bind_method(D_METHOD("instantiate", "edit_state"), &PackedScene::instantiate, DEFVAL(GEN_EDIT_STATE_DISABLED)); + ClassDB::bind_method(D_METHOD("can_instantiate"), &PackedScene::can_instantiate); ClassDB::bind_method(D_METHOD("_set_bundled_scene"), &PackedScene::_set_bundled_scene); ClassDB::bind_method(D_METHOD("_get_bundled_scene"), &PackedScene::_get_bundled_scene); ClassDB::bind_method(D_METHOD("get_state"), &PackedScene::get_state); diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h index 78a0aeaa9a..55708f7914 100644 --- a/scene/resources/packed_scene.h +++ b/scene/resources/packed_scene.h @@ -34,8 +34,8 @@ #include "core/io/resource.h" #include "scene/main/node.h" -class SceneState : public Reference { - GDCLASS(SceneState, Reference); +class SceneState : public RefCounted { + GDCLASS(SceneState, RefCounted); Vector<StringName> names; Vector<Variant> variants; @@ -136,8 +136,8 @@ public: void clear(); - bool can_instance() const; - Node *instance(GenEditState p_edit_state) const; + bool can_instantiate() const; + Node *instantiate(GenEditState p_edit_state) const; //unbuild API @@ -213,8 +213,8 @@ public: void clear(); - bool can_instance() const; - Node *instance(GenEditState p_edit_state = GEN_EDIT_STATE_DISABLED) const; + bool can_instantiate() const; + Node *instantiate(GenEditState p_edit_state = GEN_EDIT_STATE_DISABLED) const; void recreate_state(); void replace_state(Ref<SceneState> p_by); diff --git a/scene/resources/particles_material.cpp b/scene/resources/particles_material.cpp index 195ce070a7..00bea312b3 100644 --- a/scene/resources/particles_material.cpp +++ b/scene/resources/particles_material.cpp @@ -289,7 +289,7 @@ void ParticlesMaterial::_update_shader() { code += "}\n"; code += "\n"; - code += "void compute() {\n"; + code += "void start() {\n"; code += " uint base_number = NUMBER;\n"; code += " uint alt_seed = hash(base_number + uint(1) + RANDOM_SEED);\n"; code += " float angle_rand = rand_from_seed(alt_seed);\n"; @@ -305,97 +305,94 @@ void ParticlesMaterial::_update_shader() { code += " ivec2 emission_tex_size = textureSize(emission_texture_points, 0);\n"; code += " ivec2 emission_tex_ofs = ivec2(point % emission_tex_size.x, point / emission_tex_size.x);\n"; } - code += " float tv = 0.0;\n"; - code += " if (RESTART) {\n"; - if (tex_parameters[PARAM_ANGLE].is_valid()) { - code += " float tex_angle = textureLod(angle_texture, vec2(0.0, 0.0), 0.0).r;\n"; + code += " float tex_angle = textureLod(angle_texture, vec2(0.0, 0.0), 0.0).r;\n"; } else { - code += " float tex_angle = 0.0;\n"; + code += " float tex_angle = 0.0;\n"; } if (tex_parameters[PARAM_ANIM_OFFSET].is_valid()) { - code += " float tex_anim_offset = textureLod(anim_offset_texture, vec2(0.0, 0.0), 0.0).r;\n"; + code += " float tex_anim_offset = textureLod(anim_offset_texture, vec2(0.0, 0.0), 0.0).r;\n"; } else { - code += " float tex_anim_offset = 0.0;\n"; + code += " float tex_anim_offset = 0.0;\n"; } - code += " float spread_rad = spread * degree_to_rad;\n"; + code += " float spread_rad = spread * degree_to_rad;\n"; - code += " if (RESTART_VELOCITY) {\n"; + code += " if (RESTART_VELOCITY) {\n"; if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { - code += " float tex_linear_velocity = textureLod(linear_velocity_texture, vec2(0.0, 0.0), 0.0).r;\n"; + code += " float tex_linear_velocity = textureLod(linear_velocity_texture, vec2(0.0, 0.0), 0.0).r;\n"; } else { - code += " float tex_linear_velocity = 0.0;\n"; + code += " float tex_linear_velocity = 0.0;\n"; } if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; - code += " angle1_rad += direction.x != 0.0 ? atan(direction.y, direction.x) : sign(direction.y) * (pi / 2.0);\n"; - code += " vec3 rot = vec3(cos(angle1_rad), sin(angle1_rad), 0.0);\n"; - code += " VELOCITY = rot * initial_linear_velocity * mix(1.0, rand_from_seed(alt_seed), initial_linear_velocity_random);\n"; + code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; + code += " angle1_rad += direction.x != 0.0 ? atan(direction.y, direction.x) : sign(direction.y) * (pi / 2.0);\n"; + code += " vec3 rot = vec3(cos(angle1_rad), sin(angle1_rad), 0.0);\n"; + code += " VELOCITY = rot * initial_linear_velocity * mix(1.0, rand_from_seed(alt_seed), initial_linear_velocity_random);\n"; } else { //initiate velocity spread in 3D - code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; - code += " float angle2_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad * (1.0 - flatness);\n"; - code += " vec3 direction_xz = vec3(sin(angle1_rad), 0.0, cos(angle1_rad));\n"; - code += " vec3 direction_yz = vec3(0.0, sin(angle2_rad), cos(angle2_rad));\n"; - code += " direction_yz.z = direction_yz.z / max(0.0001,sqrt(abs(direction_yz.z))); // better uniform distribution\n"; - code += " vec3 spread_direction = vec3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z);\n"; - code += " vec3 direction_nrm = normalize(direction);\n"; - code += " // rotate spread to direction\n"; - code += " vec3 binormal = cross(vec3(0.0, 1.0, 0.0), direction_nrm);\n"; - code += " if (length(binormal) < 0.0001) {\n"; - code += " // direction is parallel to Y. Choose Z as the binormal.\n"; - code += " binormal = vec3(0.0, 0.0, 1.0);\n"; - code += " }\n"; - code += " binormal = normalize(binormal);\n"; - code += " vec3 normal = cross(binormal, direction_nrm);\n"; - code += " spread_direction = binormal * spread_direction.x + normal * spread_direction.y + direction_nrm * spread_direction.z;\n"; - code += " VELOCITY = spread_direction * initial_linear_velocity * mix(1.0, rand_from_seed(alt_seed), initial_linear_velocity_random);\n"; + code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; + code += " float angle2_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad * (1.0 - flatness);\n"; + code += " vec3 direction_xz = vec3(sin(angle1_rad), 0.0, cos(angle1_rad));\n"; + code += " vec3 direction_yz = vec3(0.0, sin(angle2_rad), cos(angle2_rad));\n"; + code += " direction_yz.z = direction_yz.z / max(0.0001,sqrt(abs(direction_yz.z))); // better uniform distribution\n"; + code += " vec3 spread_direction = vec3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z);\n"; + code += " vec3 direction_nrm = normalize(direction);\n"; + code += " // rotate spread to direction\n"; + code += " vec3 binormal = cross(vec3(0.0, 1.0, 0.0), direction_nrm);\n"; + code += " if (length(binormal) < 0.0001) {\n"; + code += " // direction is parallel to Y. Choose Z as the binormal.\n"; + code += " binormal = vec3(0.0, 0.0, 1.0);\n"; + code += " }\n"; + code += " binormal = normalize(binormal);\n"; + code += " vec3 normal = cross(binormal, direction_nrm);\n"; + code += " spread_direction = binormal * spread_direction.x + normal * spread_direction.y + direction_nrm * spread_direction.z;\n"; + code += " VELOCITY = spread_direction * initial_linear_velocity * mix(1.0, rand_from_seed(alt_seed), initial_linear_velocity_random);\n"; } - code += " }\n"; + code += " }\n"; - code += " float base_angle = (initial_angle + tex_angle) * mix(1.0, angle_rand, initial_angle_random);\n"; - code += " CUSTOM.x = base_angle * degree_to_rad;\n"; // angle - code += " CUSTOM.y = 0.0;\n"; // phase - code += " CUSTOM.w = (1.0 - lifetime_randomness * rand_from_seed(alt_seed));\n"; - code += " CUSTOM.z = (anim_offset + tex_anim_offset) * mix(1.0, anim_offset_rand, anim_offset_random);\n"; // animation offset (0-1) + code += " float base_angle = (initial_angle + tex_angle) * mix(1.0, angle_rand, initial_angle_random);\n"; + code += " CUSTOM.x = base_angle * degree_to_rad;\n"; // angle + code += " CUSTOM.y = 0.0;\n"; // phase + code += " CUSTOM.w = (1.0 - lifetime_randomness * rand_from_seed(alt_seed));\n"; + code += " CUSTOM.z = (anim_offset + tex_anim_offset) * mix(1.0, anim_offset_rand, anim_offset_random);\n"; // animation offset (0-1) - code += " if (RESTART_POSITION) {\n"; + code += " if (RESTART_POSITION) {\n"; switch (emission_shape) { case EMISSION_SHAPE_POINT: { //do none, identity (will later be multiplied by emission transform) - code += " TRANSFORM = mat4(vec4(1,0,0,0),vec4(0,1,0,0),vec4(0,0,1,0),vec4(0,0,0,1));\n"; + code += " TRANSFORM = mat4(vec4(1,0,0,0),vec4(0,1,0,0),vec4(0,0,1,0),vec4(0,0,0,1));\n"; } break; case EMISSION_SHAPE_SPHERE: { - code += " float s = rand_from_seed(alt_seed) * 2.0 - 1.0;\n"; - code += " float t = rand_from_seed(alt_seed) * 2.0 * pi;\n"; - code += " float radius = emission_sphere_radius * sqrt(1.0 - s * s);\n"; - code += " TRANSFORM[3].xyz = vec3(radius * cos(t), radius * sin(t), emission_sphere_radius * s);\n"; + code += " float s = rand_from_seed(alt_seed) * 2.0 - 1.0;\n"; + code += " float t = rand_from_seed(alt_seed) * 2.0 * pi;\n"; + code += " float radius = emission_sphere_radius * sqrt(1.0 - s * s);\n"; + code += " TRANSFORM[3].xyz = vec3(radius * cos(t), radius * sin(t), emission_sphere_radius * s);\n"; } break; case EMISSION_SHAPE_BOX: { - code += " TRANSFORM[3].xyz = vec3(rand_from_seed(alt_seed) * 2.0 - 1.0, rand_from_seed(alt_seed) * 2.0 - 1.0, rand_from_seed(alt_seed) * 2.0 - 1.0) * emission_box_extents;\n"; + code += " TRANSFORM[3].xyz = vec3(rand_from_seed(alt_seed) * 2.0 - 1.0, rand_from_seed(alt_seed) * 2.0 - 1.0, rand_from_seed(alt_seed) * 2.0 - 1.0) * emission_box_extents;\n"; } break; case EMISSION_SHAPE_POINTS: case EMISSION_SHAPE_DIRECTED_POINTS: { - code += " TRANSFORM[3].xyz = texelFetch(emission_texture_points, emission_tex_ofs, 0).xyz;\n"; + code += " TRANSFORM[3].xyz = texelFetch(emission_texture_points, emission_tex_ofs, 0).xyz;\n"; if (emission_shape == EMISSION_SHAPE_DIRECTED_POINTS) { if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - code += " mat2 rotm;"; - code += " rotm[0] = texelFetch(emission_texture_normal, emission_tex_ofs, 0).xy;\n"; - code += " rotm[1] = rotm[0].yx * vec2(1.0, -1.0);\n"; - code += " if (RESTART_VELOCITY) VELOCITY.xy = rotm * VELOCITY.xy;\n"; + code += " mat2 rotm;"; + code += " rotm[0] = texelFetch(emission_texture_normal, emission_tex_ofs, 0).xy;\n"; + code += " rotm[1] = rotm[0].yx * vec2(1.0, -1.0);\n"; + code += " if (RESTART_VELOCITY) VELOCITY.xy = rotm * VELOCITY.xy;\n"; } else { - code += " vec3 normal = texelFetch(emission_texture_normal, emission_tex_ofs, 0).xyz;\n"; - code += " vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);\n"; - code += " vec3 tangent = normalize(cross(v0, normal));\n"; - code += " vec3 bitangent = normalize(cross(tangent, normal));\n"; - code += " if (RESTART_VELOCITY) VELOCITY = mat3(tangent, bitangent, normal) * VELOCITY;\n"; + code += " vec3 normal = texelFetch(emission_texture_normal, emission_tex_ofs, 0).xyz;\n"; + code += " vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);\n"; + code += " vec3 tangent = normalize(cross(v0, normal));\n"; + code += " vec3 bitangent = normalize(cross(tangent, normal));\n"; + code += " if (RESTART_VELOCITY) VELOCITY = mat3(tangent, bitangent, normal) * VELOCITY;\n"; } } } break; @@ -404,134 +401,144 @@ void ParticlesMaterial::_update_shader() { } } - code += " if (RESTART_VELOCITY) VELOCITY = (EMISSION_TRANSFORM * vec4(VELOCITY, 0.0)).xyz;\n"; - code += " TRANSFORM = EMISSION_TRANSFORM * TRANSFORM;\n"; + code += " if (RESTART_VELOCITY) VELOCITY = (EMISSION_TRANSFORM * vec4(VELOCITY, 0.0)).xyz;\n"; + code += " TRANSFORM = EMISSION_TRANSFORM * TRANSFORM;\n"; if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - code += " VELOCITY.z = 0.0;\n"; - code += " TRANSFORM[3].z = 0.0;\n"; + code += " VELOCITY.z = 0.0;\n"; + code += " TRANSFORM[3].z = 0.0;\n"; } - code += " }\n"; + code += " }\n"; + code += "}\n\n"; - code += " } else {\n"; + code += "void process() {\n"; + code += " uint base_number = NUMBER;\n"; + code += " uint alt_seed = hash(base_number + uint(1) + RANDOM_SEED);\n"; + code += " float angle_rand = rand_from_seed(alt_seed);\n"; + code += " float scale_rand = rand_from_seed(alt_seed);\n"; + code += " float hue_rot_rand = rand_from_seed(alt_seed);\n"; + code += " float anim_offset_rand = rand_from_seed(alt_seed);\n"; + code += " float pi = 3.14159;\n"; + code += " float degree_to_rad = pi / 180.0;\n"; + code += "\n"; - code += " CUSTOM.y += DELTA / LIFETIME;\n"; - code += " tv = CUSTOM.y / CUSTOM.w;\n"; + code += " CUSTOM.y += DELTA / LIFETIME;\n"; + code += " float tv = CUSTOM.y / CUSTOM.w;\n"; if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { - code += " float tex_linear_velocity = textureLod(linear_velocity_texture, vec2(tv, 0.0), 0.0).r;\n"; + code += " float tex_linear_velocity = textureLod(linear_velocity_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_linear_velocity = 0.0;\n"; + code += " float tex_linear_velocity = 0.0;\n"; } if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { if (tex_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { - code += " float tex_orbit_velocity = textureLod(orbit_velocity_texture, vec2(tv, 0.0), 0.0).r;\n"; + code += " float tex_orbit_velocity = textureLod(orbit_velocity_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_orbit_velocity = 0.0;\n"; + code += " float tex_orbit_velocity = 0.0;\n"; } } if (tex_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { - code += " float tex_angular_velocity = textureLod(angular_velocity_texture, vec2(tv, 0.0), 0.0).r;\n"; + code += " float tex_angular_velocity = textureLod(angular_velocity_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_angular_velocity = 0.0;\n"; + code += " float tex_angular_velocity = 0.0;\n"; } if (tex_parameters[PARAM_LINEAR_ACCEL].is_valid()) { - code += " float tex_linear_accel = textureLod(linear_accel_texture, vec2(tv, 0.0), 0.0).r;\n"; + code += " float tex_linear_accel = textureLod(linear_accel_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_linear_accel = 0.0;\n"; + code += " float tex_linear_accel = 0.0;\n"; } if (tex_parameters[PARAM_RADIAL_ACCEL].is_valid()) { - code += " float tex_radial_accel = textureLod(radial_accel_texture, vec2(tv, 0.0), 0.0).r;\n"; + code += " float tex_radial_accel = textureLod(radial_accel_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_radial_accel = 0.0;\n"; + code += " float tex_radial_accel = 0.0;\n"; } if (tex_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) { - code += " float tex_tangent_accel = textureLod(tangent_accel_texture, vec2(tv, 0.0), 0.0).r;\n"; + code += " float tex_tangent_accel = textureLod(tangent_accel_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_tangent_accel = 0.0;\n"; + code += " float tex_tangent_accel = 0.0;\n"; } if (tex_parameters[PARAM_DAMPING].is_valid()) { - code += " float tex_damping = textureLod(damping_texture, vec2(tv, 0.0), 0.0).r;\n"; + code += " float tex_damping = textureLod(damping_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_damping = 0.0;\n"; + code += " float tex_damping = 0.0;\n"; } if (tex_parameters[PARAM_ANGLE].is_valid()) { - code += " float tex_angle = textureLod(angle_texture, vec2(tv, 0.0), 0.0).r;\n"; + code += " float tex_angle = textureLod(angle_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_angle = 0.0;\n"; + code += " float tex_angle = 0.0;\n"; } if (tex_parameters[PARAM_ANIM_SPEED].is_valid()) { - code += " float tex_anim_speed = textureLod(anim_speed_texture, vec2(tv, 0.0), 0.0).r;\n"; + code += " float tex_anim_speed = textureLod(anim_speed_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_anim_speed = 0.0;\n"; + code += " float tex_anim_speed = 0.0;\n"; } if (tex_parameters[PARAM_ANIM_OFFSET].is_valid()) { - code += " float tex_anim_offset = textureLod(anim_offset_texture, vec2(tv, 0.0), 0.0).r;\n"; + code += " float tex_anim_offset = textureLod(anim_offset_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_anim_offset = 0.0;\n"; + code += " float tex_anim_offset = 0.0;\n"; } - code += " vec3 force = gravity;\n"; - code += " vec3 pos = TRANSFORM[3].xyz;\n"; + code += " vec3 force = gravity;\n"; + code += " vec3 pos = TRANSFORM[3].xyz;\n"; if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - code += " pos.z = 0.0;\n"; - } - code += " // apply linear acceleration\n"; - code += " force += length(VELOCITY) > 0.0 ? normalize(VELOCITY) * (linear_accel + tex_linear_accel) * mix(1.0, rand_from_seed(alt_seed), linear_accel_random) : vec3(0.0);\n"; - code += " // apply radial acceleration\n"; - code += " vec3 org = EMISSION_TRANSFORM[3].xyz;\n"; - code += " vec3 diff = pos - org;\n"; - code += " force += length(diff) > 0.0 ? normalize(diff) * (radial_accel + tex_radial_accel) * mix(1.0, rand_from_seed(alt_seed), radial_accel_random) : vec3(0.0);\n"; - code += " // apply tangential acceleration;\n"; + code += " pos.z = 0.0;\n"; + } + code += " // apply linear acceleration\n"; + code += " force += length(VELOCITY) > 0.0 ? normalize(VELOCITY) * (linear_accel + tex_linear_accel) * mix(1.0, rand_from_seed(alt_seed), linear_accel_random) : vec3(0.0);\n"; + code += " // apply radial acceleration\n"; + code += " vec3 org = EMISSION_TRANSFORM[3].xyz;\n"; + code += " vec3 diff = pos - org;\n"; + code += " force += length(diff) > 0.0 ? normalize(diff) * (radial_accel + tex_radial_accel) * mix(1.0, rand_from_seed(alt_seed), radial_accel_random) : vec3(0.0);\n"; + code += " // apply tangential acceleration;\n"; if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - code += " force += length(diff.yx) > 0.0 ? vec3(normalize(diff.yx * vec2(-1.0, 1.0)), 0.0) * ((tangent_accel + tex_tangent_accel) * mix(1.0, rand_from_seed(alt_seed), tangent_accel_random)) : vec3(0.0);\n"; + code += " force += length(diff.yx) > 0.0 ? vec3(normalize(diff.yx * vec2(-1.0, 1.0)), 0.0) * ((tangent_accel + tex_tangent_accel) * mix(1.0, rand_from_seed(alt_seed), tangent_accel_random)) : vec3(0.0);\n"; } else { - code += " vec3 crossDiff = cross(normalize(diff), normalize(gravity));\n"; - code += " force += length(crossDiff) > 0.0 ? normalize(crossDiff) * ((tangent_accel + tex_tangent_accel) * mix(1.0, rand_from_seed(alt_seed), tangent_accel_random)) : vec3(0.0);\n"; + code += " vec3 crossDiff = cross(normalize(diff), normalize(gravity));\n"; + code += " force += length(crossDiff) > 0.0 ? normalize(crossDiff) * ((tangent_accel + tex_tangent_accel) * mix(1.0, rand_from_seed(alt_seed), tangent_accel_random)) : vec3(0.0);\n"; } if (attractor_interaction_enabled) { - code += " force += ATTRACTOR_FORCE;\n\n"; + code += " force += ATTRACTOR_FORCE;\n\n"; } - code += " // apply attractor forces\n"; - code += " VELOCITY += force * DELTA;\n"; - code += " // orbit velocity\n"; + code += " // apply attractor forces\n"; + code += " VELOCITY += force * DELTA;\n"; + code += " // orbit velocity\n"; if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - code += " float orbit_amount = (orbit_velocity + tex_orbit_velocity) * mix(1.0, rand_from_seed(alt_seed), orbit_velocity_random);\n"; - code += " if (orbit_amount != 0.0) {\n"; - code += " float ang = orbit_amount * DELTA * pi * 2.0;\n"; - code += " mat2 rot = mat2(vec2(cos(ang), -sin(ang)), vec2(sin(ang), cos(ang)));\n"; - code += " TRANSFORM[3].xy -= diff.xy;\n"; - code += " TRANSFORM[3].xy += rot * diff.xy;\n"; - code += " }\n"; + code += " float orbit_amount = (orbit_velocity + tex_orbit_velocity) * mix(1.0, rand_from_seed(alt_seed), orbit_velocity_random);\n"; + code += " if (orbit_amount != 0.0) {\n"; + code += " float ang = orbit_amount * DELTA * pi * 2.0;\n"; + code += " mat2 rot = mat2(vec2(cos(ang), -sin(ang)), vec2(sin(ang), cos(ang)));\n"; + code += " TRANSFORM[3].xy -= diff.xy;\n"; + code += " TRANSFORM[3].xy += rot * diff.xy;\n"; + code += " }\n"; } if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { - code += " VELOCITY = normalize(VELOCITY) * tex_linear_velocity;\n"; - } - code += " if (damping + tex_damping > 0.0) {\n"; - code += " float v = length(VELOCITY);\n"; - code += " float damp = (damping + tex_damping) * mix(1.0, rand_from_seed(alt_seed), damping_random);\n"; - code += " v -= damp * DELTA;\n"; - code += " if (v < 0.0) {\n"; - code += " VELOCITY = vec3(0.0);\n"; - code += " } else {\n"; - code += " VELOCITY = normalize(VELOCITY) * v;\n"; - code += " }\n"; + code += " VELOCITY = normalize(VELOCITY) * tex_linear_velocity;\n"; + } + code += " if (damping + tex_damping > 0.0) {\n"; + code += " float v = length(VELOCITY);\n"; + code += " float damp = (damping + tex_damping) * mix(1.0, rand_from_seed(alt_seed), damping_random);\n"; + code += " v -= damp * DELTA;\n"; + code += " if (v < 0.0) {\n"; + code += " VELOCITY = vec3(0.0);\n"; + code += " } else {\n"; + code += " VELOCITY = normalize(VELOCITY) * v;\n"; code += " }\n"; - code += " float base_angle = (initial_angle + tex_angle) * mix(1.0, angle_rand, initial_angle_random);\n"; - code += " base_angle += CUSTOM.y * LIFETIME * (angular_velocity + tex_angular_velocity) * mix(1.0, rand_from_seed(alt_seed) * 2.0 - 1.0, angular_velocity_random);\n"; - code += " CUSTOM.x = base_angle * degree_to_rad;\n"; // angle - code += " CUSTOM.z = (anim_offset + tex_anim_offset) * mix(1.0, anim_offset_rand, anim_offset_random) + CUSTOM.y * (anim_speed + tex_anim_speed) * mix(1.0, rand_from_seed(alt_seed), anim_speed_random);\n"; // angle code += " }\n"; + code += " float base_angle = (initial_angle + tex_angle) * mix(1.0, angle_rand, initial_angle_random);\n"; + code += " base_angle += CUSTOM.y * LIFETIME * (angular_velocity + tex_angular_velocity) * mix(1.0, rand_from_seed(alt_seed) * 2.0 - 1.0, angular_velocity_random);\n"; + code += " CUSTOM.x = base_angle * degree_to_rad;\n"; // angle + code += " CUSTOM.z = (anim_offset + tex_anim_offset) * mix(1.0, anim_offset_rand, anim_offset_random) + CUSTOM.y * (anim_speed + tex_anim_speed) * mix(1.0, rand_from_seed(alt_seed), anim_speed_random);\n"; // angle + // apply color // apply hue rotation if (tex_parameters[PARAM_SCALE].is_valid()) { @@ -562,7 +569,7 @@ void ParticlesMaterial::_update_shader() { code += " vec4(1.250, -1.050, -0.203, 0.0),\n"; code += " vec4(0.000, 0.000, 0.000, 0.0)) * hue_rot_s;\n"; if (color_ramp.is_valid()) { - code += " COLOR = hue_rot_mat * textureLod(color_ramp, vec2(tv, 0.0), 0.0);\n"; + code += " COLOR = hue_rot_mat * textureLod(color_ramp, vec2(tv, 0.0), 0.0) * color_value;\n"; } else { code += " COLOR = hue_rot_mat * color_value;\n"; } @@ -608,7 +615,7 @@ void ParticlesMaterial::_update_shader() { } // turn particle by rotation in Y if (particle_flags[PARTICLE_FLAG_ROTATE_Y]) { - code += " TRANSFORM = TRANSFORM * mat4(vec4(cos(CUSTOM.x), 0.0, -sin(CUSTOM.x), 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(sin(CUSTOM.x), 0.0, cos(CUSTOM.x), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; + code += " TRANSFORM = mat4(vec4(cos(CUSTOM.x), 0.0, -sin(CUSTOM.x), 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(sin(CUSTOM.x), 0.0, cos(CUSTOM.x), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; } } //scale by scale @@ -659,7 +666,7 @@ void ParticlesMaterial::_update_shader() { code += " }"; } - code += " if (CUSTOM.y > CUSTOM.w) {"; + code += " if (CUSTOM.y > CUSTOM.w) {\n"; code += " ACTIVE = false;\n"; code += " }\n"; code += "}\n"; @@ -845,52 +852,54 @@ void ParticlesMaterial::set_param_texture(Parameter p_param, const Ref<Texture2D tex_parameters[p_param] = p_texture; + RID tex_rid = p_texture.is_valid() ? p_texture->get_rid() : RID(); + switch (p_param) { case PARAM_INITIAL_LINEAR_VELOCITY: { //do none for this one } break; case PARAM_ANGULAR_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angular_velocity_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angular_velocity_texture, tex_rid); _adjust_curve_range(p_texture, -360, 360); } break; case PARAM_ORBIT_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->orbit_velocity_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->orbit_velocity_texture, tex_rid); _adjust_curve_range(p_texture, -500, 500); } break; case PARAM_LINEAR_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->linear_accel_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->linear_accel_texture, tex_rid); _adjust_curve_range(p_texture, -200, 200); } break; case PARAM_RADIAL_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_accel_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_accel_texture, tex_rid); _adjust_curve_range(p_texture, -200, 200); } break; case PARAM_TANGENTIAL_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->tangent_accel_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->tangent_accel_texture, tex_rid); _adjust_curve_range(p_texture, -200, 200); } break; case PARAM_DAMPING: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->damping_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->damping_texture, tex_rid); _adjust_curve_range(p_texture, 0, 100); } break; case PARAM_ANGLE: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angle_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angle_texture, tex_rid); _adjust_curve_range(p_texture, -360, 360); } break; case PARAM_SCALE: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_texture, tex_rid); _adjust_curve_range(p_texture, 0, 1); } break; case PARAM_HUE_VARIATION: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->hue_variation_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->hue_variation_texture, tex_rid); _adjust_curve_range(p_texture, -1, 1); } break; case PARAM_ANIM_SPEED: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_speed_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_speed_texture, tex_rid); _adjust_curve_range(p_texture, 0, 200); } break; case PARAM_ANIM_OFFSET: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset_texture, tex_rid); } break; case PARAM_MAX: break; // Can't happen, but silences warning @@ -916,7 +925,8 @@ Color ParticlesMaterial::get_color() const { void ParticlesMaterial::set_color_ramp(const Ref<Texture2D> &p_texture) { color_ramp = p_texture; - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->color_ramp, p_texture); + RID tex_rid = p_texture.is_valid() ? p_texture->get_rid() : RID(); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->color_ramp, tex_rid); _queue_shader_change(); notify_property_list_changed(); } @@ -958,17 +968,20 @@ void ParticlesMaterial::set_emission_box_extents(Vector3 p_extents) { void ParticlesMaterial::set_emission_point_texture(const Ref<Texture2D> &p_points) { emission_point_texture = p_points; - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_points, p_points); + RID tex_rid = p_points.is_valid() ? p_points->get_rid() : RID(); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_points, tex_rid); } void ParticlesMaterial::set_emission_normal_texture(const Ref<Texture2D> &p_normals) { emission_normal_texture = p_normals; - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_normal, p_normals); + RID tex_rid = p_normals.is_valid() ? p_normals->get_rid() : RID(); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_normal, tex_rid); } void ParticlesMaterial::set_emission_color_texture(const Ref<Texture2D> &p_colors) { emission_color_texture = p_colors; - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_color, p_colors); + RID tex_rid = p_colors.is_valid() ? p_colors->get_rid() : RID(); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_color, tex_rid); _queue_shader_change(); } @@ -1033,40 +1046,36 @@ RID ParticlesMaterial::get_shader_rid() const { } void ParticlesMaterial::_validate_property(PropertyInfo &property) const { - if (property.name == "color" && color_ramp.is_valid()) { - property.usage = 0; - } - if (property.name == "emission_sphere_radius" && emission_shape != EMISSION_SHAPE_SPHERE) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name == "emission_box_extents" && emission_shape != EMISSION_SHAPE_BOX) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if ((property.name == "emission_point_texture" || property.name == "emission_color_texture") && (emission_shape < EMISSION_SHAPE_POINTS)) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name == "emission_normal_texture" && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name == "emission_point_count" && (emission_shape != EMISSION_SHAPE_POINTS && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS)) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name == "sub_emitter_frequency" && sub_emitter_mode != SUB_EMITTER_CONSTANT) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name == "sub_emitter_amount_at_end" && sub_emitter_mode != SUB_EMITTER_AT_END) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name.begins_with("orbit_") && !particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } } @@ -1285,7 +1294,7 @@ void ParticlesMaterial::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_DAMPING); ADD_GROUP("Angle", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater"), "set_param", "get_param", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param", "get_param", PARAM_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANGLE); ADD_GROUP("Scale", ""); @@ -1309,7 +1318,7 @@ void ParticlesMaterial::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_offset_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANIM_OFFSET); ADD_GROUP("Sub Emitter", "sub_emitter_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_mode", PROPERTY_HINT_ENUM, "Disabled,Constant,AtEnd,AtCollision"), "set_sub_emitter_mode", "get_sub_emitter_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_mode", PROPERTY_HINT_ENUM, "Disabled,Constant,At End,At Collision"), "set_sub_emitter_mode", "get_sub_emitter_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sub_emitter_frequency", PROPERTY_HINT_RANGE, "0.01,100,0.01"), "set_sub_emitter_frequency", "get_sub_emitter_frequency"); ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_amount_at_end", PROPERTY_HINT_RANGE, "1,32,1"), "set_sub_emitter_amount_at_end", "get_sub_emitter_amount_at_end"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sub_emitter_keep_velocity"), "set_sub_emitter_keep_velocity", "get_sub_emitter_keep_velocity"); @@ -1384,7 +1393,7 @@ ParticlesMaterial::ParticlesMaterial() : set_sub_emitter_keep_velocity(false); set_attractor_interaction_enabled(true); - set_collision_enabled(true); + set_collision_enabled(false); set_collision_bounce(0.0); set_collision_friction(0.0); set_collision_use_scale(false); diff --git a/scene/resources/physics_material.cpp b/scene/resources/physics_material.cpp index d65b0c8927..31df35aa51 100644 --- a/scene/resources/physics_material.cpp +++ b/scene/resources/physics_material.cpp @@ -43,9 +43,9 @@ void PhysicsMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("set_absorbent", "absorbent"), &PhysicsMaterial::set_absorbent); ClassDB::bind_method(D_METHOD("is_absorbent"), &PhysicsMaterial::is_absorbent); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "friction", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_friction", "get_friction"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "friction", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater"), "set_friction", "get_friction"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rough"), "set_rough", "is_rough"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_bounce", "get_bounce"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bounce", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater"), "set_bounce", "get_bounce"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "absorbent"), "set_absorbent", "is_absorbent"); } diff --git a/scene/resources/polygon_path_finder.cpp b/scene/resources/polygon_path_finder.cpp index 536318ed16..a08684a506 100644 --- a/scene/resources/polygon_path_finder.cpp +++ b/scene/resources/polygon_path_finder.cpp @@ -368,7 +368,7 @@ Vector<Vector2> PolygonPathFinder::find_path(const Vector2 &p_from, const Vector path.push_back(points[at].pos); } while (at != aidx); - path.invert(); + path.reverse(); } for (int i = 0; i < points.size() - 2; i++) { diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index ba6c4591c9..dfa45cc810 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -175,6 +175,9 @@ StringName PrimitiveMesh::get_blend_shape_name(int p_index) const { return StringName(); } +void PrimitiveMesh::set_blend_shape_name(int p_index, const StringName &p_name) { +} + AABB PrimitiveMesh::get_aabb() const { if (pending_request) { _update(); @@ -204,7 +207,7 @@ void PrimitiveMesh::_bind_methods() { 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, "ShaderMaterial,StandardMaterial3D"), "set_material", "get_material"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,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"); } @@ -711,7 +714,7 @@ int BoxMesh::get_subdivide_depth() const { BoxMesh::BoxMesh() {} /** - CylinderMesh + CylinderMesh */ void CylinderMesh::_create_mesh_array(Array &p_arr) const { @@ -863,9 +866,9 @@ void CylinderMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CylinderMesh::set_rings); ClassDB::bind_method(D_METHOD("get_rings"), &CylinderMesh::get_rings); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "top_radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_top_radius", "get_top_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bottom_radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_bottom_radius", "get_bottom_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "top_radius", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_top_radius", "get_top_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bottom_radius", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_bottom_radius", "get_bottom_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_height", "get_height"); ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments"); ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings"); } @@ -952,7 +955,7 @@ void PlaneMesh::_create_mesh_array(Array &p_arr) const { u /= (subdivide_w + 1.0); v /= (subdivide_d + 1.0); - points.push_back(Vector3(-x, 0.0, -z)); + points.push_back(Vector3(-x, 0.0, -z) + center_offset); normals.push_back(Vector3(0.0, 1.0, 0.0)); ADD_TANGENT(1.0, 0.0, 0.0, 1.0); uvs.push_back(Vector2(1.0 - u, 1.0 - v)); /* 1.0 - uv to match orientation with Quad */ @@ -990,10 +993,13 @@ void PlaneMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_subdivide_width"), &PlaneMesh::get_subdivide_width); ClassDB::bind_method(D_METHOD("set_subdivide_depth", "subdivide"), &PlaneMesh::set_subdivide_depth); ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &PlaneMesh::get_subdivide_depth); + ClassDB::bind_method(D_METHOD("set_center_offset", "offset"), &PlaneMesh::set_center_offset); + ClassDB::bind_method(D_METHOD("get_center_offset"), &PlaneMesh::get_center_offset); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width"); ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_offset"), "set_center_offset", "get_center_offset"); } void PlaneMesh::set_size(const Size2 &p_size) { @@ -1023,6 +1029,15 @@ int PlaneMesh::get_subdivide_depth() const { return subdivide_d; } +void PlaneMesh::set_center_offset(const Vector3 p_offset) { + center_offset = p_offset; + _request_update(); +} + +Vector3 PlaneMesh::get_center_offset() const { + return center_offset; +} + PlaneMesh::PlaneMesh() {} /** @@ -1130,7 +1145,7 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { Vector3 normal_left, normal_right; normal_left = Vector3(-size.y, size.x * left_to_right, 0.0); - normal_right = Vector3(size.y, size.x * left_to_right, 0.0); + normal_right = Vector3(size.y, size.x * (1.0 - left_to_right), 0.0); normal_left.normalize(); normal_right.normalize(); @@ -1323,10 +1338,10 @@ void QuadMesh::_create_mesh_array(Array &p_arr) const { Vector2 _size = Vector2(size.x / 2.0f, size.y / 2.0f); Vector3 quad_faces[4] = { - Vector3(-_size.x, -_size.y, 0), - Vector3(-_size.x, _size.y, 0), - Vector3(_size.x, _size.y, 0), - Vector3(_size.x, -_size.y, 0), + Vector3(-_size.x, -_size.y, 0) + center_offset, + Vector3(-_size.x, _size.y, 0) + center_offset, + Vector3(_size.x, _size.y, 0) + center_offset, + Vector3(_size.x, -_size.y, 0) + center_offset, }; static const int indices[6] = { @@ -1362,7 +1377,11 @@ void QuadMesh::_create_mesh_array(Array &p_arr) const { void QuadMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_size", "size"), &QuadMesh::set_size); ClassDB::bind_method(D_METHOD("get_size"), &QuadMesh::get_size); + ClassDB::bind_method(D_METHOD("set_center_offset", "center_offset"), &QuadMesh::set_center_offset); + ClassDB::bind_method(D_METHOD("get_center_offset"), &QuadMesh::get_center_offset); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_offset"), "set_center_offset", "get_center_offset"); } QuadMesh::QuadMesh() { @@ -1378,6 +1397,15 @@ Size2 QuadMesh::get_size() const { return size; } +void QuadMesh::set_center_offset(Vector3 p_center_offset) { + center_offset = p_center_offset; + _request_update(); +} + +Vector3 QuadMesh::get_center_offset() const { + return center_offset; +} + /** SphereMesh */ @@ -1535,3 +1563,552 @@ void PointMesh::_create_mesh_array(Array &p_arr) const { PointMesh::PointMesh() { primitive_type = PRIMITIVE_POINTS; } +// TUBE TRAIL + +void TubeTrailMesh::set_radius(const float p_radius) { + radius = p_radius; + _request_update(); +} +float TubeTrailMesh::get_radius() const { + return radius; +} + +void TubeTrailMesh::set_radial_steps(const int p_radial_steps) { + ERR_FAIL_COND(p_radial_steps < 3 || p_radial_steps > 128); + radial_steps = p_radial_steps; + _request_update(); +} +int TubeTrailMesh::get_radial_steps() const { + return radial_steps; +} + +void TubeTrailMesh::set_sections(const int p_sections) { + ERR_FAIL_COND(p_sections < 2 || p_sections > 128); + sections = p_sections; + _request_update(); +} +int TubeTrailMesh::get_sections() const { + return sections; +} + +void TubeTrailMesh::set_section_length(float p_section_length) { + section_length = p_section_length; + _request_update(); +} +float TubeTrailMesh::get_section_length() const { + return section_length; +} + +void TubeTrailMesh::set_section_rings(const int p_section_rings) { + ERR_FAIL_COND(p_section_rings < 1 || p_section_rings > 1024); + section_rings = p_section_rings; + _request_update(); +} +int TubeTrailMesh::get_section_rings() const { + return section_rings; +} + +void TubeTrailMesh::set_curve(const Ref<Curve> &p_curve) { + if (curve == p_curve) { + return; + } + if (curve.is_valid()) { + curve->disconnect("changed", callable_mp(this, &TubeTrailMesh::_curve_changed)); + } + curve = p_curve; + if (curve.is_valid()) { + curve->connect("changed", callable_mp(this, &TubeTrailMesh::_curve_changed)); + } + _request_update(); +} +Ref<Curve> TubeTrailMesh::get_curve() const { + return curve; +} + +void TubeTrailMesh::_curve_changed() { + _request_update(); +} +int TubeTrailMesh::get_builtin_bind_pose_count() const { + return sections + 1; +} + +Transform3D TubeTrailMesh::get_builtin_bind_pose(int p_index) const { + float depth = section_length * sections; + + Transform3D xform; + xform.origin.y = depth / 2.0 - section_length * float(p_index); + xform.origin.y = -xform.origin.y; //bind is an inverse transform, so negate y + + return xform; +} + +void TubeTrailMesh::_create_mesh_array(Array &p_arr) const { + PackedVector3Array points; + PackedVector3Array normals; + PackedFloat32Array tangents; + PackedVector2Array uvs; + PackedInt32Array bone_indices; + PackedFloat32Array bone_weights; + PackedInt32Array indices; + + int point = 0; + +#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ + tangents.push_back(m_x); \ + tangents.push_back(m_y); \ + tangents.push_back(m_z); \ + tangents.push_back(m_d); + + int thisrow = 0; + int prevrow = 0; + + int total_rings = section_rings * sections; + float depth = section_length * sections; + + for (int j = 0; j <= total_rings; j++) { + float v = j; + v /= total_rings; + + float y = depth * v; + y = (depth * 0.5) - y; + + int bone = j / section_rings; + float blend = 1.0 - float(j % section_rings) / float(section_rings); + + for (int i = 0; i <= radial_steps; i++) { + float u = i; + u /= radial_steps; + + float r = radius; + if (curve.is_valid() && curve->get_point_count() > 0) { + r *= curve->interpolate_baked(v); + } + float x = sin(u * Math_TAU); + float z = cos(u * Math_TAU); + + Vector3 p = Vector3(x * r, y, z * r); + points.push_back(p); + normals.push_back(Vector3(x, 0, z)); + ADD_TANGENT(z, 0.0, -x, 1.0) + uvs.push_back(Vector2(u, v * 0.5)); + point++; + { + bone_indices.push_back(bone); + bone_indices.push_back(MIN(sections, bone + 1)); + bone_indices.push_back(0); + bone_indices.push_back(0); + + bone_weights.push_back(blend); + bone_weights.push_back(1.0 - blend); + bone_weights.push_back(0); + bone_weights.push_back(0); + } + + if (i > 0 && j > 0) { + indices.push_back(prevrow + i - 1); + indices.push_back(prevrow + i); + indices.push_back(thisrow + i - 1); + + indices.push_back(prevrow + i); + indices.push_back(thisrow + i); + indices.push_back(thisrow + i - 1); + } + } + + prevrow = thisrow; + thisrow = point; + } + + // add top + float scale_pos = 1.0; + if (curve.is_valid() && curve->get_point_count() > 0) { + scale_pos = curve->interpolate_baked(0); + } + + if (scale_pos > CMP_EPSILON) { + float y = depth * 0.5; + + thisrow = point; + points.push_back(Vector3(0.0, y, 0)); + normals.push_back(Vector3(0.0, 1.0, 0.0)); + ADD_TANGENT(1.0, 0.0, 0.0, 1.0) + uvs.push_back(Vector2(0.25, 0.75)); + point++; + + bone_indices.push_back(0); + bone_indices.push_back(0); + bone_indices.push_back(0); + bone_indices.push_back(0); + + bone_weights.push_back(1.0); + bone_weights.push_back(0); + bone_weights.push_back(0); + bone_weights.push_back(0); + + float rm = radius * scale_pos; + + for (int i = 0; i <= radial_steps; i++) { + float r = i; + r /= radial_steps; + + float x = sin(r * Math_TAU); + float z = cos(r * Math_TAU); + + float u = ((x + 1.0) * 0.25); + float v = 0.5 + ((z + 1.0) * 0.25); + + Vector3 p = Vector3(x * rm, y, z * rm); + points.push_back(p); + normals.push_back(Vector3(0.0, 1.0, 0.0)); + ADD_TANGENT(1.0, 0.0, 0.0, 1.0) + uvs.push_back(Vector2(u, v)); + point++; + + bone_indices.push_back(0); + bone_indices.push_back(0); + bone_indices.push_back(0); + bone_indices.push_back(0); + + bone_weights.push_back(1.0); + bone_weights.push_back(0); + bone_weights.push_back(0); + bone_weights.push_back(0); + + if (i > 0) { + indices.push_back(thisrow); + indices.push_back(point - 1); + indices.push_back(point - 2); + }; + }; + }; + + float scale_neg = 1.0; + if (curve.is_valid() && curve->get_point_count() > 0) { + scale_neg = curve->interpolate_baked(1.0); + } + + // add bottom + if (scale_neg > CMP_EPSILON) { + float y = depth * -0.5; + + thisrow = point; + points.push_back(Vector3(0.0, y, 0.0)); + normals.push_back(Vector3(0.0, -1.0, 0.0)); + ADD_TANGENT(1.0, 0.0, 0.0, 1.0) + uvs.push_back(Vector2(0.75, 0.75)); + point++; + + bone_indices.push_back(sections); + bone_indices.push_back(0); + bone_indices.push_back(0); + bone_indices.push_back(0); + + bone_weights.push_back(1.0); + bone_weights.push_back(0); + bone_weights.push_back(0); + bone_weights.push_back(0); + + float rm = radius * scale_neg; + + for (int i = 0; i <= radial_steps; i++) { + float r = i; + r /= radial_steps; + + float x = sin(r * Math_TAU); + float z = cos(r * Math_TAU); + + float u = 0.5 + ((x + 1.0) * 0.25); + float v = 1.0 - ((z + 1.0) * 0.25); + + Vector3 p = Vector3(x * rm, y, z * rm); + points.push_back(p); + normals.push_back(Vector3(0.0, -1.0, 0.0)); + ADD_TANGENT(1.0, 0.0, 0.0, 1.0) + uvs.push_back(Vector2(u, v)); + point++; + + bone_indices.push_back(sections); + bone_indices.push_back(0); + bone_indices.push_back(0); + bone_indices.push_back(0); + + bone_weights.push_back(1.0); + bone_weights.push_back(0); + bone_weights.push_back(0); + bone_weights.push_back(0); + + if (i > 0) { + indices.push_back(thisrow); + indices.push_back(point - 2); + indices.push_back(point - 1); + }; + }; + }; + + p_arr[RS::ARRAY_VERTEX] = points; + p_arr[RS::ARRAY_NORMAL] = normals; + p_arr[RS::ARRAY_TANGENT] = tangents; + p_arr[RS::ARRAY_TEX_UV] = uvs; + p_arr[RS::ARRAY_BONES] = bone_indices; + p_arr[RS::ARRAY_WEIGHTS] = bone_weights; + p_arr[RS::ARRAY_INDEX] = indices; +} + +void TubeTrailMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &TubeTrailMesh::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &TubeTrailMesh::get_radius); + + ClassDB::bind_method(D_METHOD("set_radial_steps", "radial_steps"), &TubeTrailMesh::set_radial_steps); + ClassDB::bind_method(D_METHOD("get_radial_steps"), &TubeTrailMesh::get_radial_steps); + + ClassDB::bind_method(D_METHOD("set_sections", "sections"), &TubeTrailMesh::set_sections); + ClassDB::bind_method(D_METHOD("get_sections"), &TubeTrailMesh::get_sections); + + ClassDB::bind_method(D_METHOD("set_section_length", "section_length"), &TubeTrailMesh::set_section_length); + ClassDB::bind_method(D_METHOD("get_section_length"), &TubeTrailMesh::get_section_length); + + ClassDB::bind_method(D_METHOD("set_section_rings", "section_rings"), &TubeTrailMesh::set_section_rings); + ClassDB::bind_method(D_METHOD("get_section_rings"), &TubeTrailMesh::get_section_rings); + + ClassDB::bind_method(D_METHOD("set_curve", "curve"), &TubeTrailMesh::set_curve); + ClassDB::bind_method(D_METHOD("get_curve"), &TubeTrailMesh::get_curve); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_radius", "get_radius"); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_steps", PROPERTY_HINT_RANGE, "3,128,1"), "set_radial_steps", "get_radial_steps"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "sections", PROPERTY_HINT_RANGE, "2,128,1"), "set_sections", "get_sections"); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "section_length", PROPERTY_HINT_RANGE, "0.001,1024.0,0.001,or_greater"), "set_section_length", "get_section_length"); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "section_rings", PROPERTY_HINT_RANGE, "1,128,1"), "set_section_rings", "get_section_rings"); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve"); +} + +TubeTrailMesh::TubeTrailMesh() { +} + +// TUBE TRAIL + +void RibbonTrailMesh::set_shape(Shape p_shape) { + shape = p_shape; + _request_update(); +} +RibbonTrailMesh::Shape RibbonTrailMesh::get_shape() const { + return shape; +} + +void RibbonTrailMesh::set_size(const float p_size) { + size = p_size; + _request_update(); +} +float RibbonTrailMesh::get_size() const { + return size; +} + +void RibbonTrailMesh::set_sections(const int p_sections) { + ERR_FAIL_COND(p_sections < 2 || p_sections > 128); + sections = p_sections; + _request_update(); +} +int RibbonTrailMesh::get_sections() const { + return sections; +} + +void RibbonTrailMesh::set_section_length(float p_section_length) { + section_length = p_section_length; + _request_update(); +} +float RibbonTrailMesh::get_section_length() const { + return section_length; +} + +void RibbonTrailMesh::set_section_segments(const int p_section_segments) { + ERR_FAIL_COND(p_section_segments < 1 || p_section_segments > 1024); + section_segments = p_section_segments; + _request_update(); +} +int RibbonTrailMesh::get_section_segments() const { + return section_segments; +} + +void RibbonTrailMesh::set_curve(const Ref<Curve> &p_curve) { + if (curve == p_curve) { + return; + } + if (curve.is_valid()) { + curve->disconnect("changed", callable_mp(this, &RibbonTrailMesh::_curve_changed)); + } + curve = p_curve; + if (curve.is_valid()) { + curve->connect("changed", callable_mp(this, &RibbonTrailMesh::_curve_changed)); + } + _request_update(); +} +Ref<Curve> RibbonTrailMesh::get_curve() const { + return curve; +} + +void RibbonTrailMesh::_curve_changed() { + _request_update(); +} +int RibbonTrailMesh::get_builtin_bind_pose_count() const { + return sections + 1; +} + +Transform3D RibbonTrailMesh::get_builtin_bind_pose(int p_index) const { + float depth = section_length * sections; + + Transform3D xform; + xform.origin.y = depth / 2.0 - section_length * float(p_index); + xform.origin.y = -xform.origin.y; //bind is an inverse transform, so negate y + + return xform; +} + +void RibbonTrailMesh::_create_mesh_array(Array &p_arr) const { + PackedVector3Array points; + PackedVector3Array normals; + PackedFloat32Array tangents; + PackedVector2Array uvs; + PackedInt32Array bone_indices; + PackedFloat32Array bone_weights; + PackedInt32Array indices; + +#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ + tangents.push_back(m_x); \ + tangents.push_back(m_y); \ + tangents.push_back(m_z); \ + tangents.push_back(m_d); + + int total_segments = section_segments * sections; + float depth = section_length * sections; + + for (int j = 0; j <= total_segments; j++) { + float v = j; + v /= total_segments; + + float y = depth * v; + y = (depth * 0.5) - y; + + int bone = j / section_segments; + float blend = 1.0 - float(j % section_segments) / float(section_segments); + + float s = size; + + if (curve.is_valid() && curve->get_point_count() > 0) { + s *= curve->interpolate_baked(v); + } + + points.push_back(Vector3(-s * 0.5, y, 0)); + points.push_back(Vector3(+s * 0.5, y, 0)); + if (shape == SHAPE_CROSS) { + points.push_back(Vector3(0, y, -s * 0.5)); + points.push_back(Vector3(0, y, +s * 0.5)); + } + + normals.push_back(Vector3(0, 0, 1)); + normals.push_back(Vector3(0, 0, 1)); + if (shape == SHAPE_CROSS) { + normals.push_back(Vector3(1, 0, 0)); + normals.push_back(Vector3(1, 0, 0)); + } + + uvs.push_back(Vector2(0, v)); + uvs.push_back(Vector2(1, v)); + if (shape == SHAPE_CROSS) { + uvs.push_back(Vector2(0, v)); + uvs.push_back(Vector2(1, v)); + } + + ADD_TANGENT(0.0, 1.0, 0.0, 1.0) + ADD_TANGENT(0.0, 1.0, 0.0, 1.0) + if (shape == SHAPE_CROSS) { + ADD_TANGENT(0.0, 1.0, 0.0, 1.0) + ADD_TANGENT(0.0, 1.0, 0.0, 1.0) + } + + for (int i = 0; i < (shape == SHAPE_CROSS ? 4 : 2); i++) { + bone_indices.push_back(bone); + bone_indices.push_back(MIN(sections, bone + 1)); + bone_indices.push_back(0); + bone_indices.push_back(0); + + bone_weights.push_back(blend); + bone_weights.push_back(1.0 - blend); + bone_weights.push_back(0); + bone_weights.push_back(0); + } + + if (j > 0) { + if (shape == SHAPE_CROSS) { + int base = j * 4 - 4; + indices.push_back(base + 0); + indices.push_back(base + 1); + indices.push_back(base + 4); + + indices.push_back(base + 1); + indices.push_back(base + 5); + indices.push_back(base + 4); + + indices.push_back(base + 2); + indices.push_back(base + 3); + indices.push_back(base + 6); + + indices.push_back(base + 3); + indices.push_back(base + 7); + indices.push_back(base + 6); + } else { + int base = j * 2 - 2; + indices.push_back(base + 0); + indices.push_back(base + 1); + indices.push_back(base + 2); + + indices.push_back(base + 1); + indices.push_back(base + 3); + indices.push_back(base + 2); + } + } + } + + p_arr[RS::ARRAY_VERTEX] = points; + p_arr[RS::ARRAY_NORMAL] = normals; + p_arr[RS::ARRAY_TANGENT] = tangents; + p_arr[RS::ARRAY_TEX_UV] = uvs; + p_arr[RS::ARRAY_BONES] = bone_indices; + p_arr[RS::ARRAY_WEIGHTS] = bone_weights; + p_arr[RS::ARRAY_INDEX] = indices; +} + +void RibbonTrailMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_size", "size"), &RibbonTrailMesh::set_size); + ClassDB::bind_method(D_METHOD("get_size"), &RibbonTrailMesh::get_size); + + ClassDB::bind_method(D_METHOD("set_sections", "sections"), &RibbonTrailMesh::set_sections); + ClassDB::bind_method(D_METHOD("get_sections"), &RibbonTrailMesh::get_sections); + + ClassDB::bind_method(D_METHOD("set_section_length", "section_length"), &RibbonTrailMesh::set_section_length); + ClassDB::bind_method(D_METHOD("get_section_length"), &RibbonTrailMesh::get_section_length); + + ClassDB::bind_method(D_METHOD("set_section_segments", "section_segments"), &RibbonTrailMesh::set_section_segments); + ClassDB::bind_method(D_METHOD("get_section_segments"), &RibbonTrailMesh::get_section_segments); + + ClassDB::bind_method(D_METHOD("set_curve", "curve"), &RibbonTrailMesh::set_curve); + ClassDB::bind_method(D_METHOD("get_curve"), &RibbonTrailMesh::get_curve); + + ClassDB::bind_method(D_METHOD("set_shape", "shape"), &RibbonTrailMesh::set_shape); + ClassDB::bind_method(D_METHOD("get_shape"), &RibbonTrailMesh::get_shape); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "shape", PROPERTY_HINT_ENUM, "Flat,Cross"), "set_shape", "get_shape"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "sections", PROPERTY_HINT_RANGE, "2,128,1"), "set_sections", "get_sections"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "section_length", PROPERTY_HINT_RANGE, "0.001,1024.0,0.001,or_greater"), "set_section_length", "get_section_length"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "section_segments", PROPERTY_HINT_RANGE, "1,128,1"), "set_section_segments", "get_section_segments"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve"); + + BIND_ENUM_CONSTANT(SHAPE_FLAT) + BIND_ENUM_CONSTANT(SHAPE_CROSS) +} + +RibbonTrailMesh::RibbonTrailMesh() { +} diff --git a/scene/resources/primitive_meshes.h b/scene/resources/primitive_meshes.h index bb3df9d10e..a3de34d3e3 100644 --- a/scene/resources/primitive_meshes.h +++ b/scene/resources/primitive_meshes.h @@ -81,6 +81,7 @@ public: virtual Ref<Material> surface_get_material(int p_idx) const override; virtual int get_blend_shape_count() const override; virtual StringName get_blend_shape_name(int p_index) const override; + virtual void set_blend_shape_name(int p_index, const StringName &p_name) override; virtual AABB get_aabb() const override; virtual RID get_rid() const override; @@ -210,6 +211,7 @@ private: Size2 size = Size2(2.0, 2.0); int subdivide_w = 0; int subdivide_d = 0; + Vector3 center_offset; protected: static void _bind_methods(); @@ -225,6 +227,9 @@ public: void set_subdivide_depth(const int p_divisions); int get_subdivide_depth() const; + void set_center_offset(const Vector3 p_offset); + Vector3 get_center_offset() const; + PlaneMesh(); }; @@ -273,6 +278,7 @@ class QuadMesh : public PrimitiveMesh { private: Size2 size = Size2(1.0, 1.0); + Vector3 center_offset; protected: static void _bind_methods(); @@ -283,6 +289,9 @@ public: void set_size(const Size2 &p_size); Size2 get_size() const; + + void set_center_offset(const Vector3 p_offset); + Vector3 get_center_offset() const; }; /** @@ -335,4 +344,98 @@ public: PointMesh(); }; +class TubeTrailMesh : public PrimitiveMesh { + GDCLASS(TubeTrailMesh, PrimitiveMesh); + +private: + float radius = 1.0; + int radial_steps = 8; + int sections = 5; + float section_length = 0.2; + int section_rings = 3; + + Ref<Curve> curve; + + void _curve_changed(); + +protected: + static void _bind_methods(); + virtual void _create_mesh_array(Array &p_arr) const override; + +public: + void set_radius(const float p_radius); + float get_radius() const; + + void set_radial_steps(const int p_radial_steps); + int get_radial_steps() const; + + void set_sections(const int p_sections); + int get_sections() const; + + void set_section_length(float p_sectionlength); + float get_section_length() const; + + void set_section_rings(const int p_section_rings); + int get_section_rings() const; + + void set_curve(const Ref<Curve> &p_curve); + Ref<Curve> get_curve() const; + + virtual int get_builtin_bind_pose_count() const override; + virtual Transform3D get_builtin_bind_pose(int p_index) const override; + + TubeTrailMesh(); +}; + +class RibbonTrailMesh : public PrimitiveMesh { + GDCLASS(RibbonTrailMesh, PrimitiveMesh); + +public: + enum Shape { + SHAPE_FLAT, + SHAPE_CROSS + }; + +private: + float size = 1.0; + int sections = 5; + float section_length = 0.2; + int section_segments = 3; + + Shape shape = SHAPE_CROSS; + + Ref<Curve> curve; + + void _curve_changed(); + +protected: + static void _bind_methods(); + virtual void _create_mesh_array(Array &p_arr) const override; + +public: + void set_shape(Shape p_shape); + Shape get_shape() const; + + void set_size(const float p_size); + float get_size() const; + + void set_sections(const int p_sections); + int get_sections() const; + + void set_section_length(float p_sectionlength); + float get_section_length() const; + + void set_section_segments(const int p_section_segments); + int get_section_segments() const; + + void set_curve(const Ref<Curve> &p_curve); + Ref<Curve> get_curve() const; + + virtual int get_builtin_bind_pose_count() const override; + virtual Transform3D get_builtin_bind_pose(int p_index) const override; + + RibbonTrailMesh(); +}; + +VARIANT_ENUM_CAST(RibbonTrailMesh::Shape) #endif diff --git a/scene/resources/rectangle_shape_2d.cpp b/scene/resources/rectangle_shape_2d.cpp index dc4c6dc2d7..17ce0b34ac 100644 --- a/scene/resources/rectangle_shape_2d.cpp +++ b/scene/resources/rectangle_shape_2d.cpp @@ -37,6 +37,26 @@ void RectangleShape2D::_update_shape() { emit_changed(); } +#ifndef DISABLE_DEPRECATED +bool RectangleShape2D::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "extents") { // Compatibility with Godot 3.x. + // Convert to `size`, twice as big. + set_size((Vector2)p_value * 2); + return true; + } + return false; +} + +bool RectangleShape2D::_get(const StringName &p_name, Variant &r_property) const { + if (p_name == "extents") { // Compatibility with Godot 3.x. + // Convert to `extents`, half as big. + r_property = size / 2; + return true; + } + return false; +} +#endif // DISABLE_DEPRECATED + void RectangleShape2D::set_size(const Vector2 &p_size) { size = p_size; _update_shape(); diff --git a/scene/resources/rectangle_shape_2d.h b/scene/resources/rectangle_shape_2d.h index 8d747c86af..f1e8be4c5b 100644 --- a/scene/resources/rectangle_shape_2d.h +++ b/scene/resources/rectangle_shape_2d.h @@ -41,6 +41,10 @@ class RectangleShape2D : public Shape2D { protected: static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_property) const; +#endif // DISABLE_DEPRECATED public: void set_size(const Vector2 &p_size); diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index f2751b7604..ee61e64ed3 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -31,14 +31,14 @@ #include "resource_format_text.h" #include "core/config/project_settings.h" +#include "core/io/dir_access.h" #include "core/io/resource_format_binary.h" -#include "core/os/dir_access.h" #include "core/version.h" //version 2: changed names for basis, aabb, Vectors, etc. #define FORMAT_VERSION 2 -#include "core/os/dir_access.h" +#include "core/io/dir_access.h" #include "core/version.h" #define _printerr() ERR_PRINT(String(res_path + ":" + itos(lines) + " - Parse Error: " + error_text).utf8().get_data()); @@ -65,7 +65,7 @@ Error ResourceLoaderText::_parse_sub_resource_dummy(DummyReadData *p_data, Varia if (!p_data->resource_map.has(index)) { Ref<DummyResource> dr; - dr.instance(); + dr.instantiate(); dr->set_subindex(index); p_data->resource_map[index] = dr; p_data->resource_set.insert(dr); @@ -183,7 +183,7 @@ Error ResourceLoaderText::_parse_ext_resource(VariantParser::Stream *p_stream, R Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourceParser &parser) { Ref<PackedScene> packed_scene; - packed_scene.instance(); + packed_scene.instantiate(); while (true) { if (next_tag.name == "node") { @@ -208,7 +208,7 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars if (next_tag.fields.has("type")) { type = packed_scene->get_state()->add_name(next_tag.fields["type"]); } else { - type = SceneState::TYPE_INSTANCED; //no type? assume this was instanced + type = SceneState::TYPE_INSTANCED; //no type? assume this was instantiated } if (next_tag.fields.has("instance")) { @@ -522,7 +522,7 @@ Error ResourceLoaderText::load() { } else { //create - Object *obj = ClassDB::instance(type); + Object *obj = ClassDB::instantiate(type); if (!obj) { error_text += "Can't create sub resource of type: " + type; _printerr(); @@ -575,6 +575,7 @@ Error ResourceLoaderText::load() { int_resources[id] = res; //always assign int resources if (do_assign && cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { res->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); + res->set_subindex(id); } if (progress && resources_total > 0) { @@ -603,7 +604,7 @@ Error ResourceLoaderText::load() { } if (!resource.is_valid()) { - Object *obj = ClassDB::instance(res_type); + Object *obj = ClassDB::instantiate(res_type); if (!obj) { error_text += "Can't create sub resource of type: " + res_type; _printerr(); @@ -978,7 +979,7 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) } wf->store_32(0); //string table size, will not be in use - size_t ext_res_count_pos = wf->get_position(); + uint64_t ext_res_count_pos = wf->get_position(); wf->store_32(0); //zero ext resources, still parsing them @@ -1021,7 +1022,7 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) int lindex = dummy_read.external_resources.size(); Ref<DummyResource> dr; - dr.instance(); + dr.instantiate(); dr->set_path("res://dummy" + itos(lindex)); //anything is good to detect it for saving as external dummy_read.external_resources[dr] = lindex; dummy_read.rev_external_resources[index] = dr; @@ -1041,7 +1042,7 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) //now, save resources to a separate file, for now - size_t sub_res_count_pos = wf->get_position(); + uint64_t sub_res_count_pos = wf->get_position(); wf->store_32(0); //zero sub resources, still parsing them String temp_file = p_path + ".temp"; @@ -1050,8 +1051,8 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) return ERR_CANT_OPEN; } - Vector<size_t> local_offsets; - Vector<size_t> local_pointers_pos; + Vector<uint64_t> local_offsets; + Vector<uint64_t> local_pointers_pos; while (next_tag.name == "sub_resource" || next_tag.name == "resource") { String type; @@ -1089,7 +1090,7 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) wf->store_64(0); //temp local offset bs_save_unicode_string(wf2, type); - size_t propcount_ofs = wf2->get_position(); + uint64_t propcount_ofs = wf2->get_position(); wf2->store_32(0); int prop_count = 0; @@ -1159,7 +1160,7 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) local_offsets.push_back(wf2->get_position()); bs_save_unicode_string(wf2, "PackedScene"); - size_t propcount_ofs = wf2->get_position(); + uint64_t propcount_ofs = wf2->get_position(); wf2->store_32(0); int prop_count = 0; @@ -1185,7 +1186,7 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) wf2->close(); - size_t offset_from = wf->get_position(); + uint64_t offset_from = wf->get_position(); wf->seek(sub_res_count_pos); //plus one because the saved one wf->store_32(local_offsets.size()); diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h index 2dc683415d..f5d9cca859 100644 --- a/scene/resources/resource_format_text.h +++ b/scene/resources/resource_format_text.h @@ -31,9 +31,9 @@ #ifndef RESOURCE_FORMAT_TEXT_H #define RESOURCE_FORMAT_TEXT_H +#include "core/io/file_access.h" #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" -#include "core/os/file_access.h" #include "core/variant/variant_parser.h" #include "scene/resources/packed_scene.h" diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp index 77c6199794..f19d08dbb1 100644 --- a/scene/resources/shader.cpp +++ b/scene/resources/shader.cpp @@ -30,7 +30,7 @@ #include "shader.h" -#include "core/os/file_access.h" +#include "core/io/file_access.h" #include "scene/scene_string_names.h" #include "servers/rendering/shader_language.h" #include "servers/rendering_server.h" @@ -167,7 +167,7 @@ RES ResourceFormatLoaderShader::load(const String &p_path, const String &p_origi } Ref<Shader> shader; - shader.instance(); + shader.instantiate(); Vector<uint8_t> buffer = FileAccess::get_file_as_array(p_path); @@ -184,7 +184,7 @@ RES ResourceFormatLoaderShader::load(const String &p_path, const String &p_origi } void ResourceFormatLoaderShader::get_recognized_extensions(List<String> *p_extensions) const { - p_extensions->push_back("shader"); + p_extensions->push_back("gdshader"); } bool ResourceFormatLoaderShader::handles_type(const String &p_type) const { @@ -193,7 +193,7 @@ bool ResourceFormatLoaderShader::handles_type(const String &p_type) const { String ResourceFormatLoaderShader::get_resource_type(const String &p_path) const { String el = p_path.get_extension().to_lower(); - if (el == "shader") { + if (el == "gdshader") { return "Shader"; } return ""; @@ -224,7 +224,7 @@ Error ResourceFormatSaverShader::save(const String &p_path, const RES &p_resourc void ResourceFormatSaverShader::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const { if (const Shader *shader = Object::cast_to<Shader>(*p_resource)) { if (shader->is_text_shader()) { - p_extensions->push_back("shader"); + p_extensions->push_back("gdshader"); } } } diff --git a/scene/resources/shape_3d.cpp b/scene/resources/shape_3d.cpp index 5761a405ce..a02a0e5488 100644 --- a/scene/resources/shape_3d.cpp +++ b/scene/resources/shape_3d.cpp @@ -35,7 +35,7 @@ #include "scene/resources/mesh.h" #include "servers/physics_server_3d.h" -void Shape3D::add_vertices_to_array(Vector<Vector3> &array, const Transform &p_xform) { +void Shape3D::add_vertices_to_array(Vector<Vector3> &array, const Transform3D &p_xform) { Vector<Vector3> toadd = get_debug_mesh_lines(); if (toadd.size()) { @@ -102,6 +102,8 @@ void Shape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_margin", "margin"), &Shape3D::set_margin); ClassDB::bind_method(D_METHOD("get_margin"), &Shape3D::get_margin); + ClassDB::bind_method(D_METHOD("get_debug_mesh"), &Shape3D::get_debug_mesh); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0.001,10,0.001"), "set_margin", "get_margin"); } diff --git a/scene/resources/shape_3d.h b/scene/resources/shape_3d.h index 0644940fd4..b8e529cd3c 100644 --- a/scene/resources/shape_3d.h +++ b/scene/resources/shape_3d.h @@ -60,7 +60,7 @@ public: /// Returns the radius of a sphere that fully enclose this shape virtual real_t get_enclosing_radius() const = 0; - void add_vertices_to_array(Vector<Vector3> &array, const Transform &p_xform); + void add_vertices_to_array(Vector<Vector3> &array, const Transform3D &p_xform); real_t get_margin() const; void set_margin(real_t p_margin); diff --git a/scene/resources/skeleton_modification_2d.cpp b/scene/resources/skeleton_modification_2d.cpp new file mode 100644 index 0000000000..b52a60006a --- /dev/null +++ b/scene/resources/skeleton_modification_2d.cpp @@ -0,0 +1,253 @@ +/*************************************************************************/ +/* skeleton_modification_2d.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "skeleton_modification_2d.h" +#include "scene/2d/skeleton_2d.h" + +#include "scene/2d/collision_object_2d.h" +#include "scene/2d/collision_shape_2d.h" +#include "scene/2d/physical_bone_2d.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif // TOOLS_ENABLED + +/////////////////////////////////////// +// Modification2D +/////////////////////////////////////// + +void SkeletonModification2D::_execute(float p_delta) { + call("_execute", p_delta); + + if (!enabled) { + return; + } +} + +void SkeletonModification2D::_setup_modification(SkeletonModificationStack2D *p_stack) { + stack = p_stack; + if (stack) { + is_setup = true; + } else { + WARN_PRINT("Could not setup modification with name " + get_name()); + } + + call("_setup_modification", p_stack); +} + +void SkeletonModification2D::_draw_editor_gizmo() { + call("_draw_editor_gizmo"); +} + +void SkeletonModification2D::set_enabled(bool p_enabled) { + enabled = p_enabled; + +#ifdef TOOLS_ENABLED + if (editor_draw_gizmo) { + if (stack) { + stack->set_editor_gizmos_dirty(true); + } + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2D::get_enabled() { + return enabled; +} + +float SkeletonModification2D::clamp_angle(float p_angle, float p_min_bound, float p_max_bound, bool p_invert) { + // Map to the 0 to 360 range (in radians though) instead of the -180 to 180 range. + if (p_angle < 0) { + p_angle = Math_TAU + p_angle; + } + + // Make min and max in the range of 0 to 360 (in radians), and make sure they are in the right order + if (p_min_bound < 0) { + p_min_bound = Math_TAU + p_min_bound; + } + if (p_max_bound < 0) { + p_max_bound = Math_TAU + p_max_bound; + } + if (p_min_bound > p_max_bound) { + float tmp = p_min_bound; + p_min_bound = p_max_bound; + p_max_bound = tmp; + } + + // Note: May not be the most optimal way to clamp, but it always constraints to the nearest angle. + if (p_invert == false) { + if (p_angle < p_min_bound || p_angle > p_max_bound) { + Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); + Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); + Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); + + if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { + p_angle = p_min_bound; + } else { + p_angle = p_max_bound; + } + } + } else { + if (p_angle > p_min_bound && p_angle < p_max_bound) { + Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); + Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); + Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); + + if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { + p_angle = p_min_bound; + } else { + p_angle = p_max_bound; + } + } + } + return p_angle; +} + +void SkeletonModification2D::editor_draw_angle_constraints(Bone2D *p_operation_bone, float p_min_bound, float p_max_bound, + bool p_constraint_enabled, bool p_constraint_in_localspace, bool p_constraint_inverted) { + if (!p_operation_bone) { + return; + } + + Color bone_ik_color = Color(1.0, 0.65, 0.0, 0.4); +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + bone_ik_color = EditorSettings::get_singleton()->get("editors/2d/bone_ik_color"); + } +#endif // TOOLS_ENABLED + + float arc_angle_min = p_min_bound; + float arc_angle_max = p_max_bound; + if (arc_angle_min < 0) { + arc_angle_min = (Math_PI * 2) + arc_angle_min; + } + if (arc_angle_max < 0) { + arc_angle_max = (Math_PI * 2) + arc_angle_max; + } + if (arc_angle_min > arc_angle_max) { + float tmp = arc_angle_min; + arc_angle_min = arc_angle_max; + arc_angle_max = tmp; + } + arc_angle_min += p_operation_bone->get_bone_angle(); + arc_angle_max += p_operation_bone->get_bone_angle(); + + if (p_constraint_enabled) { + if (p_constraint_in_localspace) { + Node *operation_bone_parent = p_operation_bone->get_parent(); + Bone2D *operation_bone_parent_bone = Object::cast_to<Bone2D>(operation_bone_parent); + + if (operation_bone_parent_bone) { + stack->skeleton->draw_set_transform( + stack->skeleton->get_global_transform().affine_inverse().xform(p_operation_bone->get_global_position()), + operation_bone_parent_bone->get_global_rotation() - stack->skeleton->get_global_rotation()); + } else { + stack->skeleton->draw_set_transform(stack->skeleton->get_global_transform().affine_inverse().xform(p_operation_bone->get_global_position())); + } + } else { + stack->skeleton->draw_set_transform(stack->skeleton->get_global_transform().affine_inverse().xform(p_operation_bone->get_global_position())); + } + + if (p_constraint_inverted) { + stack->skeleton->draw_arc(Vector2(0, 0), p_operation_bone->get_length(), + arc_angle_min + (Math_PI * 2), arc_angle_max, 32, bone_ik_color, 1.0); + } else { + stack->skeleton->draw_arc(Vector2(0, 0), p_operation_bone->get_length(), + arc_angle_min, arc_angle_max, 32, bone_ik_color, 1.0); + } + stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(arc_angle_min), Math::sin(arc_angle_min)) * p_operation_bone->get_length(), bone_ik_color, 1.0); + stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(arc_angle_max), Math::sin(arc_angle_max)) * p_operation_bone->get_length(), bone_ik_color, 1.0); + + } else { + stack->skeleton->draw_set_transform(stack->skeleton->get_global_transform().affine_inverse().xform(p_operation_bone->get_global_position())); + stack->skeleton->draw_arc(Vector2(0, 0), p_operation_bone->get_length(), 0, Math_PI * 2, 32, bone_ik_color, 1.0); + stack->skeleton->draw_line(Vector2(0, 0), Vector2(1, 0) * p_operation_bone->get_length(), bone_ik_color, 1.0); + } +} + +Ref<SkeletonModificationStack2D> SkeletonModification2D::get_modification_stack() { + return stack; +} + +void SkeletonModification2D::set_is_setup(bool p_setup) { + is_setup = p_setup; +} + +bool SkeletonModification2D::get_is_setup() const { + return is_setup; +} + +void SkeletonModification2D::set_execution_mode(int p_mode) { + execution_mode = p_mode; +} + +int SkeletonModification2D::get_execution_mode() const { + return execution_mode; +} + +void SkeletonModification2D::set_editor_draw_gizmo(bool p_draw_gizmo) { + editor_draw_gizmo = p_draw_gizmo; +#ifdef TOOLS_ENABLED + if (is_setup) { + if (stack) { + stack->set_editor_gizmos_dirty(true); + } + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2D::get_editor_draw_gizmo() const { + return editor_draw_gizmo; +} + +void SkeletonModification2D::_bind_methods() { + BIND_VMETHOD(MethodInfo("_execute", PropertyInfo(Variant::FLOAT, "delta"))); + BIND_VMETHOD(MethodInfo("_setup_modification", PropertyInfo(Variant::OBJECT, "modification_stack", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack2D"))); + BIND_VMETHOD(MethodInfo("_draw_editor_gizmo")); + + ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModification2D::set_enabled); + ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModification2D::get_enabled); + ClassDB::bind_method(D_METHOD("get_modification_stack"), &SkeletonModification2D::get_modification_stack); + ClassDB::bind_method(D_METHOD("set_is_setup", "is_setup"), &SkeletonModification2D::set_is_setup); + ClassDB::bind_method(D_METHOD("get_is_setup"), &SkeletonModification2D::get_is_setup); + ClassDB::bind_method(D_METHOD("set_execution_mode", "execution_mode"), &SkeletonModification2D::set_execution_mode); + ClassDB::bind_method(D_METHOD("get_execution_mode"), &SkeletonModification2D::get_execution_mode); + ClassDB::bind_method(D_METHOD("clamp_angle", "angle", "min", "max", "invert"), &SkeletonModification2D::clamp_angle); + ClassDB::bind_method(D_METHOD("set_editor_draw_gizmo", "draw_gizmo"), &SkeletonModification2D::set_editor_draw_gizmo); + ClassDB::bind_method(D_METHOD("get_editor_draw_gizmo"), &SkeletonModification2D::get_editor_draw_gizmo); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "execution_mode", PROPERTY_HINT_ENUM, "process, physics_process"), "set_execution_mode", "get_execution_mode"); +} + +SkeletonModification2D::SkeletonModification2D() { + stack = nullptr; + is_setup = false; +} diff --git a/scene/resources/skeleton_modification_2d.h b/scene/resources/skeleton_modification_2d.h new file mode 100644 index 0000000000..18633e55cb --- /dev/null +++ b/scene/resources/skeleton_modification_2d.h @@ -0,0 +1,85 @@ +/*************************************************************************/ +/* skeleton_modification_2d.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SKELETONMODIFICATION2D_H +#define SKELETONMODIFICATION2D_H + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/skeleton_modification_stack_2d.h" + +/////////////////////////////////////// +// SkeletonModification2D +/////////////////////////////////////// + +class SkeletonModificationStack2D; +class Bone2D; + +class SkeletonModification2D : public Resource { + GDCLASS(SkeletonModification2D, Resource); + friend class Skeleton2D; + friend class Bone2D; + +protected: + static void _bind_methods(); + + SkeletonModificationStack2D *stack; + int execution_mode = 0; // 0 = process + + bool enabled = true; + bool is_setup = false; + + bool _print_execution_error(bool p_condition, String p_message); + +public: + virtual void _execute(float _delta); + virtual void _setup_modification(SkeletonModificationStack2D *p_stack); + virtual void _draw_editor_gizmo(); + + bool editor_draw_gizmo = false; + void set_editor_draw_gizmo(bool p_draw_gizmo); + bool get_editor_draw_gizmo() const; + + void set_enabled(bool p_enabled); + bool get_enabled(); + + Ref<SkeletonModificationStack2D> get_modification_stack(); + void set_is_setup(bool p_setup); + bool get_is_setup() const; + + void set_execution_mode(int p_mode); + int get_execution_mode() const; + + float clamp_angle(float p_angle, float p_min_bound, float p_max_bound, bool p_invert_clamp = false); + void editor_draw_angle_constraints(Bone2D *p_operation_bone, float p_min_bound, float p_max_bound, bool p_constraint_enabled, bool p_constraint_in_localspace, bool p_constraint_inverted); + + SkeletonModification2D(); +}; + +#endif // SKELETONMODIFICATION2D_H diff --git a/scene/resources/skeleton_modification_2d_ccdik.cpp b/scene/resources/skeleton_modification_2d_ccdik.cpp new file mode 100644 index 0000000000..7ea60e584e --- /dev/null +++ b/scene/resources/skeleton_modification_2d_ccdik.cpp @@ -0,0 +1,545 @@ +/*************************************************************************/ +/* skeleton_modification_2d_ccdik.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "skeleton_modification_2d_ccdik.h" +#include "scene/2d/skeleton_2d.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif // TOOLS_ENABLED + +bool SkeletonModification2DCCDIK::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("joint_data/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, ccdik_data_chain.size(), false); + + if (what == "bone2d_node") { + set_ccdik_joint_bone2d_node(which, p_value); + } else if (what == "bone_index") { + set_ccdik_joint_bone_index(which, p_value); + } else if (what == "rotate_from_joint") { + set_ccdik_joint_rotate_from_joint(which, p_value); + } else if (what == "enable_constraint") { + set_ccdik_joint_enable_constraint(which, p_value); + } else if (what == "constraint_angle_min") { + set_ccdik_joint_constraint_angle_min(which, Math::deg2rad(float(p_value))); + } else if (what == "constraint_angle_max") { + set_ccdik_joint_constraint_angle_max(which, Math::deg2rad(float(p_value))); + } else if (what == "constraint_angle_invert") { + set_ccdik_joint_constraint_angle_invert(which, p_value); + } else if (what == "constraint_in_localspace") { + set_ccdik_joint_constraint_in_localspace(which, p_value); + } + +#ifdef TOOLS_ENABLED + if (what.begins_with("editor_draw_gizmo")) { + set_ccdik_joint_editor_draw_gizmo(which, p_value); + } +#endif // TOOLS_ENABLED + + return true; + } + +#ifdef TOOLS_ENABLED + if (path.begins_with("editor/draw_gizmo")) { + set_editor_draw_gizmo(p_value); + } +#endif // TOOLS_ENABLED + + return true; +} + +bool SkeletonModification2DCCDIK::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("joint_data/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, ccdik_data_chain.size(), false); + + if (what == "bone2d_node") { + r_ret = get_ccdik_joint_bone2d_node(which); + } else if (what == "bone_index") { + r_ret = get_ccdik_joint_bone_index(which); + } else if (what == "rotate_from_joint") { + r_ret = get_ccdik_joint_rotate_from_joint(which); + } else if (what == "enable_constraint") { + r_ret = get_ccdik_joint_enable_constraint(which); + } else if (what == "constraint_angle_min") { + r_ret = Math::rad2deg(get_ccdik_joint_constraint_angle_min(which)); + } else if (what == "constraint_angle_max") { + r_ret = Math::rad2deg(get_ccdik_joint_constraint_angle_max(which)); + } else if (what == "constraint_angle_invert") { + r_ret = get_ccdik_joint_constraint_angle_invert(which); + } else if (what == "constraint_in_localspace") { + r_ret = get_ccdik_joint_constraint_in_localspace(which); + } + +#ifdef TOOLS_ENABLED + if (what.begins_with("editor_draw_gizmo")) { + r_ret = get_ccdik_joint_editor_draw_gizmo(which); + } +#endif // TOOLS_ENABLED + + return true; + } + +#ifdef TOOLS_ENABLED + if (path.begins_with("editor/draw_gizmo")) { + r_ret = get_editor_draw_gizmo(); + } +#endif // TOOLS_ENABLED + + return true; +} + +void SkeletonModification2DCCDIK::_get_property_list(List<PropertyInfo> *p_list) const { + for (int i = 0; i < ccdik_data_chain.size(); i++) { + String base_string = "joint_data/" + itos(i) + "/"; + + p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "rotate_from_joint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "enable_constraint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (ccdik_data_chain[i].enable_constraint) { + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "constraint_angle_min", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "constraint_angle_max", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "constraint_angle_invert", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "constraint_in_localspace", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "editor_draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } +#endif // TOOLS_ENABLED + } + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } +#endif // TOOLS_ENABLED +} + +void SkeletonModification2DCCDIK::_execute(float p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + + if (target_node_cache.is_null()) { + WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); + update_target_cache(); + return; + } + if (tip_node_cache.is_null()) { + WARN_PRINT_ONCE("Tip cache is out of date. Attempting to update..."); + update_tip_cache(); + return; + } + + Node2D *target = Object::cast_to<Node2D>(ObjectDB::get_instance(target_node_cache)); + if (!target || !target->is_inside_tree()) { + ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); + return; + } + + Node2D *tip = Object::cast_to<Node2D>(ObjectDB::get_instance(tip_node_cache)); + if (!tip || !tip->is_inside_tree()) { + ERR_PRINT_ONCE("Tip node is not in the scene tree. Cannot execute modification!"); + return; + } + + for (int i = 0; i < ccdik_data_chain.size(); i++) { + _execute_ccdik_joint(i, target, tip); + } +} + +void SkeletonModification2DCCDIK::_execute_ccdik_joint(int p_joint_idx, Node2D *p_target, Node2D *p_tip) { + CCDIK_Joint_Data2D ccdik_data = ccdik_data_chain[p_joint_idx]; + if (ccdik_data.bone_idx < 0 || ccdik_data.bone_idx > stack->skeleton->get_bone_count()) { + ERR_PRINT_ONCE("2D CCDIK joint: bone index not found!"); + return; + } + + Bone2D *operation_bone = stack->skeleton->get_bone(ccdik_data.bone_idx); + Transform2D operation_transform = operation_bone->get_global_transform(); + + if (ccdik_data.rotate_from_joint) { + // To rotate from the joint, simply look at the target! + operation_transform.set_rotation( + operation_transform.looking_at(p_target->get_global_transform().get_origin()).get_rotation() - operation_bone->get_bone_angle()); + } else { + // How to rotate from the tip: get the difference of rotation needed from the tip to the target, from the perspective of the joint. + // Because we are only using the offset, we do not need to account for the bone angle of the Bone2D node. + float joint_to_tip = operation_transform.get_origin().angle_to_point(p_tip->get_global_transform().get_origin()); + float joint_to_target = operation_transform.get_origin().angle_to_point(p_target->get_global_transform().get_origin()); + operation_transform.set_rotation( + operation_transform.get_rotation() + (joint_to_target - joint_to_tip)); + } + + // Reset scale + operation_transform.set_scale(operation_bone->get_global_transform().get_scale()); + + // Apply constraints in globalspace: + if (ccdik_data.enable_constraint && !ccdik_data.constraint_in_localspace) { + operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), ccdik_data.constraint_angle_min, ccdik_data.constraint_angle_max, ccdik_data.constraint_angle_invert)); + } + + // Convert from a global transform to a delta and then apply the delta to the local transform. + operation_bone->set_global_transform(operation_transform); + operation_transform = operation_bone->get_transform(); + + // Apply constraints in localspace: + if (ccdik_data.enable_constraint && ccdik_data.constraint_in_localspace) { + operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), ccdik_data.constraint_angle_min, ccdik_data.constraint_angle_max, ccdik_data.constraint_angle_invert)); + } + + // Set the local pose override, and to make sure child bones are also updated, set the transform of the bone. + stack->skeleton->set_bone_local_pose_override(ccdik_data.bone_idx, operation_transform, stack->strength, true); + operation_bone->set_transform(operation_transform); + operation_bone->notification(operation_bone->NOTIFICATION_TRANSFORM_CHANGED); +} + +void SkeletonModification2DCCDIK::_setup_modification(SkeletonModificationStack2D *p_stack) { + stack = p_stack; + + if (stack != nullptr) { + is_setup = true; + update_target_cache(); + update_tip_cache(); + } +} + +void SkeletonModification2DCCDIK::_draw_editor_gizmo() { + if (!enabled || !is_setup) { + return; + } + + for (int i = 0; i < ccdik_data_chain.size(); i++) { + if (!ccdik_data_chain[i].editor_draw_gizmo) { + continue; + } + + Bone2D *operation_bone = stack->skeleton->get_bone(ccdik_data_chain[i].bone_idx); + editor_draw_angle_constraints(operation_bone, ccdik_data_chain[i].constraint_angle_min, ccdik_data_chain[i].constraint_angle_max, + ccdik_data_chain[i].enable_constraint, ccdik_data_chain[i].constraint_in_localspace, ccdik_data_chain[i].constraint_angle_invert); + } +} + +void SkeletonModification2DCCDIK::update_target_cache() { + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + return; + } + + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: node is not in the scene tree!"); + target_node_cache = node->get_instance_id(); + } + } + } +} + +void SkeletonModification2DCCDIK::update_tip_cache() { + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update tip cache: modification is not properly setup!"); + return; + } + + tip_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(tip_node)) { + Node *node = stack->skeleton->get_node(tip_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update tip cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update tip cache: node is not in the scene tree!"); + tip_node_cache = node->get_instance_id(); + } + } + } +} + +void SkeletonModification2DCCDIK::ccdik_joint_update_bone2d_cache(int p_joint_idx) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "Cannot update bone2d cache: joint index out of range!"); + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update CCDIK Bone2D cache: modification is not properly setup!"); + return; + } + + ccdik_data_chain.write[p_joint_idx].bone2d_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(ccdik_data_chain[p_joint_idx].bone2d_node)) { + Node *node = stack->skeleton->get_node(ccdik_data_chain[p_joint_idx].bone2d_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update CCDIK joint " + itos(p_joint_idx) + " Bone2D cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update CCDIK joint " + itos(p_joint_idx) + " Bone2D cache: node is not in the scene tree!"); + ccdik_data_chain.write[p_joint_idx].bone2d_node_cache = node->get_instance_id(); + + Bone2D *bone = Object::cast_to<Bone2D>(node); + if (bone) { + ccdik_data_chain.write[p_joint_idx].bone_idx = bone->get_index_in_skeleton(); + } else { + ERR_FAIL_MSG("CCDIK joint " + itos(p_joint_idx) + " Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); + } + } + } + } +} + +void SkeletonModification2DCCDIK::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_target_cache(); +} + +NodePath SkeletonModification2DCCDIK::get_target_node() const { + return target_node; +} + +void SkeletonModification2DCCDIK::set_tip_node(const NodePath &p_tip_node) { + tip_node = p_tip_node; + update_tip_cache(); +} + +NodePath SkeletonModification2DCCDIK::get_tip_node() const { + return tip_node; +} + +void SkeletonModification2DCCDIK::set_ccdik_data_chain_length(int p_length) { + ccdik_data_chain.resize(p_length); + notify_property_list_changed(); +} + +int SkeletonModification2DCCDIK::get_ccdik_data_chain_length() { + return ccdik_data_chain.size(); +} + +void SkeletonModification2DCCDIK::set_ccdik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); + ccdik_data_chain.write[p_joint_idx].bone2d_node = p_target_node; + ccdik_joint_update_bone2d_cache(p_joint_idx); + + notify_property_list_changed(); +} + +NodePath SkeletonModification2DCCDIK::get_ccdik_joint_bone2d_node(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), NodePath(), "CCDIK joint out of range!"); + return ccdik_data_chain[p_joint_idx].bone2d_node; +} + +void SkeletonModification2DCCDIK::set_ccdik_joint_bone_index(int p_joint_idx, int p_bone_idx) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCCDIK joint out of range!"); + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + + if (is_setup) { + if (stack->skeleton) { + ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); + ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; + ccdik_data_chain.write[p_joint_idx].bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); + ccdik_data_chain.write[p_joint_idx].bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); + } else { + WARN_PRINT("Cannot verify the CCDIK joint " + itos(p_joint_idx) + " bone index for this modification..."); + ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; + } + } else { + WARN_PRINT("Cannot verify the CCDIK joint " + itos(p_joint_idx) + " bone index for this modification..."); + ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; + } + + notify_property_list_changed(); +} + +int SkeletonModification2DCCDIK::get_ccdik_joint_bone_index(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), -1, "CCDIK joint out of range!"); + return ccdik_data_chain[p_joint_idx].bone_idx; +} + +void SkeletonModification2DCCDIK::set_ccdik_joint_rotate_from_joint(int p_joint_idx, bool p_rotate_from_joint) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); + ccdik_data_chain.write[p_joint_idx].rotate_from_joint = p_rotate_from_joint; +} + +bool SkeletonModification2DCCDIK::get_ccdik_joint_rotate_from_joint(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); + return ccdik_data_chain[p_joint_idx].rotate_from_joint; +} + +void SkeletonModification2DCCDIK::set_ccdik_joint_enable_constraint(int p_joint_idx, bool p_constraint) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); + ccdik_data_chain.write[p_joint_idx].enable_constraint = p_constraint; + notify_property_list_changed(); + +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2DCCDIK::get_ccdik_joint_enable_constraint(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); + return ccdik_data_chain[p_joint_idx].enable_constraint; +} + +void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_min(int p_joint_idx, float p_angle_min) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); + ccdik_data_chain.write[p_joint_idx].constraint_angle_min = p_angle_min; + +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +float SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_min(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), 0.0, "CCDIK joint out of range!"); + return ccdik_data_chain[p_joint_idx].constraint_angle_min; +} + +void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_max(int p_joint_idx, float p_angle_max) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); + ccdik_data_chain.write[p_joint_idx].constraint_angle_max = p_angle_max; + +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +float SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_max(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), 0.0, "CCDIK joint out of range!"); + return ccdik_data_chain[p_joint_idx].constraint_angle_max; +} + +void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_invert(int p_joint_idx, bool p_invert) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); + ccdik_data_chain.write[p_joint_idx].constraint_angle_invert = p_invert; + +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_invert(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); + return ccdik_data_chain[p_joint_idx].constraint_angle_invert; +} + +void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_in_localspace(int p_joint_idx, bool p_constraint_in_localspace) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); + ccdik_data_chain.write[p_joint_idx].constraint_in_localspace = p_constraint_in_localspace; + +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2DCCDIK::get_ccdik_joint_constraint_in_localspace(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); + return ccdik_data_chain[p_joint_idx].constraint_in_localspace; +} + +void SkeletonModification2DCCDIK::set_ccdik_joint_editor_draw_gizmo(int p_joint_idx, bool p_draw_gizmo) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); + ccdik_data_chain.write[p_joint_idx].editor_draw_gizmo = p_draw_gizmo; + +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2DCCDIK::get_ccdik_joint_editor_draw_gizmo(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); + return ccdik_data_chain[p_joint_idx].editor_draw_gizmo; +} + +void SkeletonModification2DCCDIK::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DCCDIK::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DCCDIK::get_target_node); + ClassDB::bind_method(D_METHOD("set_tip_node", "tip_nodepath"), &SkeletonModification2DCCDIK::set_tip_node); + ClassDB::bind_method(D_METHOD("get_tip_node"), &SkeletonModification2DCCDIK::get_tip_node); + + ClassDB::bind_method(D_METHOD("set_ccdik_data_chain_length", "length"), &SkeletonModification2DCCDIK::set_ccdik_data_chain_length); + ClassDB::bind_method(D_METHOD("get_ccdik_data_chain_length"), &SkeletonModification2DCCDIK::get_ccdik_data_chain_length); + + ClassDB::bind_method(D_METHOD("set_ccdik_joint_bone2d_node", "joint_idx", "bone2d_nodepath"), &SkeletonModification2DCCDIK::set_ccdik_joint_bone2d_node); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_bone2d_node", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_bone2d_node); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification2DCCDIK::set_ccdik_joint_bone_index); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_bone_index", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_bone_index); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_rotate_from_joint", "joint_idx", "rotate_from_joint"), &SkeletonModification2DCCDIK::set_ccdik_joint_rotate_from_joint); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_rotate_from_joint", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_rotate_from_joint); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_enable_constraint", "joint_idx", "enable_constraint"), &SkeletonModification2DCCDIK::set_ccdik_joint_enable_constraint); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_enable_constraint", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_enable_constraint); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_min", "joint_idx", "angle_min"), &SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_min); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_min", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_min); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_max", "joint_idx", "angle_max"), &SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_max); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_max", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_max); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_invert", "joint_idx", "invert"), &SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_invert); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_invert", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_invert); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "tip_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_tip_node", "get_tip_node"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "ccdik_data_chain_length", PROPERTY_HINT_RANGE, "0, 100, 1"), "set_ccdik_data_chain_length", "get_ccdik_data_chain_length"); +} + +SkeletonModification2DCCDIK::SkeletonModification2DCCDIK() { + stack = nullptr; + is_setup = false; + enabled = true; + editor_draw_gizmo = true; +} + +SkeletonModification2DCCDIK::~SkeletonModification2DCCDIK() { +} diff --git a/scene/resources/skeleton_modification_2d_ccdik.h b/scene/resources/skeleton_modification_2d_ccdik.h new file mode 100644 index 0000000000..dc48291f62 --- /dev/null +++ b/scene/resources/skeleton_modification_2d_ccdik.h @@ -0,0 +1,116 @@ +/*************************************************************************/ +/* skeleton_modification_2d_ccdik.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SKELETONMODIFICATION2DCCDIK_H +#define SKELETONMODIFICATION2DCCDIK_H + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/skeleton_modification_2d.h" + +/////////////////////////////////////// +// SkeletonModification2DCCDIK +/////////////////////////////////////// + +class SkeletonModification2DCCDIK : public SkeletonModification2D { + GDCLASS(SkeletonModification2DCCDIK, SkeletonModification2D); + +private: + struct CCDIK_Joint_Data2D { + int bone_idx = -1; + NodePath bone2d_node; + ObjectID bone2d_node_cache; + bool rotate_from_joint = false; + + bool enable_constraint = false; + float constraint_angle_min = 0; + float constraint_angle_max = (2.0 * Math_PI); + bool constraint_angle_invert = false; + bool constraint_in_localspace = true; + + bool editor_draw_gizmo = true; + }; + + Vector<CCDIK_Joint_Data2D> ccdik_data_chain; + + NodePath target_node; + ObjectID target_node_cache; + void update_target_cache(); + + NodePath tip_node; + ObjectID tip_node_cache; + void update_tip_cache(); + + void ccdik_joint_update_bone2d_cache(int p_joint_idx); + void _execute_ccdik_joint(int p_joint_idx, Node2D *p_target, Node2D *p_tip); + +protected: + static void _bind_methods(); + bool _set(const StringName &p_path, const Variant &p_value); + bool _get(const StringName &p_path, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + void _execute(float p_delta) override; + void _setup_modification(SkeletonModificationStack2D *p_stack) override; + void _draw_editor_gizmo() override; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + void set_tip_node(const NodePath &p_tip_node); + NodePath get_tip_node() const; + + int get_ccdik_data_chain_length(); + void set_ccdik_data_chain_length(int p_new_length); + + void set_ccdik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node); + NodePath get_ccdik_joint_bone2d_node(int p_joint_idx) const; + void set_ccdik_joint_bone_index(int p_joint_idx, int p_bone_idx); + int get_ccdik_joint_bone_index(int p_joint_idx) const; + + void set_ccdik_joint_rotate_from_joint(int p_joint_idx, bool p_rotate_from_joint); + bool get_ccdik_joint_rotate_from_joint(int p_joint_idx) const; + void set_ccdik_joint_enable_constraint(int p_joint_idx, bool p_constraint); + bool get_ccdik_joint_enable_constraint(int p_joint_idx) const; + void set_ccdik_joint_constraint_angle_min(int p_joint_idx, float p_angle_min); + float get_ccdik_joint_constraint_angle_min(int p_joint_idx) const; + void set_ccdik_joint_constraint_angle_max(int p_joint_idx, float p_angle_max); + float get_ccdik_joint_constraint_angle_max(int p_joint_idx) const; + void set_ccdik_joint_constraint_angle_invert(int p_joint_idx, bool p_invert); + bool get_ccdik_joint_constraint_angle_invert(int p_joint_idx) const; + void set_ccdik_joint_constraint_in_localspace(int p_joint_idx, bool p_constraint_in_localspace); + bool get_ccdik_joint_constraint_in_localspace(int p_joint_idx) const; + void set_ccdik_joint_editor_draw_gizmo(int p_joint_idx, bool p_draw_gizmo); + bool get_ccdik_joint_editor_draw_gizmo(int p_joint_idx) const; + + SkeletonModification2DCCDIK(); + ~SkeletonModification2DCCDIK(); +}; + +#endif // SKELETONMODIFICATION2DCCDIK_H diff --git a/scene/resources/skeleton_modification_2d_fabrik.cpp b/scene/resources/skeleton_modification_2d_fabrik.cpp new file mode 100644 index 0000000000..aef852f7e4 --- /dev/null +++ b/scene/resources/skeleton_modification_2d_fabrik.cpp @@ -0,0 +1,444 @@ +/*************************************************************************/ +/* skeleton_modification_2d_fabrik.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "skeleton_modification_2d_fabrik.h" +#include "scene/2d/skeleton_2d.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif // TOOLS_ENABLED + +bool SkeletonModification2DFABRIK::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("joint_data/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, fabrik_data_chain.size(), false); + + if (what == "bone2d_node") { + set_fabrik_joint_bone2d_node(which, p_value); + } else if (what == "bone_index") { + set_fabrik_joint_bone_index(which, p_value); + } else if (what == "magnet_position") { + set_fabrik_joint_magnet_position(which, p_value); + } else if (what == "use_target_rotation") { + set_fabrik_joint_use_target_rotation(which, p_value); + } + } + + return true; +} + +bool SkeletonModification2DFABRIK::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("joint_data/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, fabrik_data_chain.size(), false); + + if (what == "bone2d_node") { + r_ret = get_fabrik_joint_bone2d_node(which); + } else if (what == "bone_index") { + r_ret = get_fabrik_joint_bone_index(which); + } else if (what == "magnet_position") { + r_ret = get_fabrik_joint_magnet_position(which); + } else if (what == "use_target_rotation") { + r_ret = get_fabrik_joint_use_target_rotation(which); + } + return true; + } + return true; +} + +void SkeletonModification2DFABRIK::_get_property_list(List<PropertyInfo> *p_list) const { + for (int i = 0; i < fabrik_data_chain.size(); i++) { + String base_string = "joint_data/" + itos(i) + "/"; + + p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); + + if (i > 0) { + p_list->push_back(PropertyInfo(Variant::VECTOR2, base_string + "magnet_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } + if (i == fabrik_data_chain.size() - 1) { + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_target_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } + } +} + +void SkeletonModification2DFABRIK::_execute(float p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + + if (target_node_cache.is_null()) { + WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); + update_target_cache(); + return; + } + + if (fabrik_data_chain.size() <= 1) { + ERR_PRINT_ONCE("FABRIK requires at least two joints to operate! Cannot execute modification!"); + return; + } + + Node2D *target = Object::cast_to<Node2D>(ObjectDB::get_instance(target_node_cache)); + if (!target || !target->is_inside_tree()) { + ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); + return; + } + target_global_pose = target->get_global_transform(); + + if (fabrik_data_chain[0].bone2d_node_cache.is_null() && !fabrik_data_chain[0].bone2d_node.is_empty()) { + fabrik_joint_update_bone2d_cache(0); + WARN_PRINT("Bone2D cache for origin joint is out of date. Updating..."); + } + + Bone2D *origin_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[0].bone2d_node_cache)); + if (!origin_bone2d_node || !origin_bone2d_node->is_inside_tree()) { + ERR_PRINT_ONCE("Origin joint's Bone2D node is not in the scene tree. Cannot execute modification!"); + return; + } + + origin_global_pose = origin_bone2d_node->get_global_transform(); + + if (fabrik_transform_chain.size() != fabrik_data_chain.size()) { + fabrik_transform_chain.resize(fabrik_data_chain.size()); + } + + for (int i = 0; i < fabrik_data_chain.size(); i++) { + // Update the transform chain + if (fabrik_data_chain[i].bone2d_node_cache.is_null() && !fabrik_data_chain[i].bone2d_node.is_empty()) { + WARN_PRINT_ONCE("Bone2D cache for joint " + itos(i) + " is out of date.. Attempting to update..."); + fabrik_joint_update_bone2d_cache(i); + } + Bone2D *joint_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); + if (!joint_bone2d_node) { + ERR_PRINT_ONCE("FABRIK Joint " + itos(i) + " does not have a Bone2D node set! Cannot execute modification!"); + return; + } + fabrik_transform_chain.write[i] = joint_bone2d_node->get_global_transform(); + + // Apply magnet positions + if (i == 0) { + continue; // The origin cannot use a magnet position! + } else { + Transform2D joint_trans = fabrik_transform_chain[i]; + joint_trans.set_origin(joint_trans.get_origin() + fabrik_data_chain[i].magnet_position); + fabrik_transform_chain.write[i] = joint_trans; + } + } + + Bone2D *final_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[fabrik_data_chain.size() - 1].bone2d_node_cache)); + float final_bone2d_angle = final_bone2d_node->get_global_transform().get_rotation(); + if (fabrik_data_chain[fabrik_data_chain.size() - 1].use_target_rotation) { + final_bone2d_angle = target_global_pose.get_rotation(); + } + Vector2 final_bone2d_direction = Vector2(Math::cos(final_bone2d_angle), Math::sin(final_bone2d_angle)); + float final_bone2d_length = final_bone2d_node->get_length() * MIN(final_bone2d_node->get_global_scale().x, final_bone2d_node->get_global_scale().y); + float target_distance = (final_bone2d_node->get_global_transform().get_origin() + (final_bone2d_direction * final_bone2d_length)).distance_to(target->get_global_transform().get_origin()); + chain_iterations = 0; + + while (target_distance > chain_tolarance) { + chain_backwards(); + chain_forwards(); + + final_bone2d_angle = final_bone2d_node->get_global_transform().get_rotation(); + if (fabrik_data_chain[fabrik_data_chain.size() - 1].use_target_rotation) { + final_bone2d_angle = target_global_pose.get_rotation(); + } + final_bone2d_direction = Vector2(Math::cos(final_bone2d_angle), Math::sin(final_bone2d_angle)); + target_distance = (final_bone2d_node->get_global_transform().get_origin() + (final_bone2d_direction * final_bone2d_length)).distance_to(target->get_global_transform().get_origin()); + + chain_iterations += 1; + if (chain_iterations >= chain_max_iterations) { + break; + } + } + + // Apply all of the saved transforms to the Bone2D nodes + for (int i = 0; i < fabrik_data_chain.size(); i++) { + Bone2D *joint_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); + if (!joint_bone2d_node) { + ERR_PRINT_ONCE("FABRIK Joint " + itos(i) + " does not have a Bone2D node set!"); + continue; + } + Transform2D chain_trans = fabrik_transform_chain[i]; + + // Apply rotation + if (i + 1 < fabrik_data_chain.size()) { + chain_trans = chain_trans.looking_at(fabrik_transform_chain[i + 1].get_origin()); + } else { + if (fabrik_data_chain[i].use_target_rotation) { + chain_trans.set_rotation(target_global_pose.get_rotation()); + } else { + chain_trans = chain_trans.looking_at(target_global_pose.get_origin()); + } + } + // Adjust for the bone angle + chain_trans.set_rotation(chain_trans.get_rotation() - joint_bone2d_node->get_bone_angle()); + + // Reset scale + chain_trans.set_scale(joint_bone2d_node->get_global_transform().get_scale()); + + // Apply to the bone, and to the override + joint_bone2d_node->set_global_transform(chain_trans); + stack->skeleton->set_bone_local_pose_override(fabrik_data_chain[i].bone_idx, joint_bone2d_node->get_transform(), stack->strength, true); + } +} + +void SkeletonModification2DFABRIK::chain_backwards() { + int final_joint_index = fabrik_data_chain.size() - 1; + Bone2D *final_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[final_joint_index].bone2d_node_cache)); + Transform2D final_bone2d_trans = fabrik_transform_chain[final_joint_index]; + + // Set the rotation of the tip bone + final_bone2d_trans = final_bone2d_trans.looking_at(target_global_pose.get_origin()); + + // Set the position of the tip bone + float final_bone2d_angle = final_bone2d_trans.get_rotation(); + if (fabrik_data_chain[final_joint_index].use_target_rotation) { + final_bone2d_angle = target_global_pose.get_rotation(); + } + Vector2 final_bone2d_direction = Vector2(Math::cos(final_bone2d_angle), Math::sin(final_bone2d_angle)); + float final_bone2d_length = final_bone2d_node->get_length() * MIN(final_bone2d_node->get_global_scale().x, final_bone2d_node->get_global_scale().y); + final_bone2d_trans.set_origin(target_global_pose.get_origin() - (final_bone2d_direction * final_bone2d_length)); + + // Save the transform + fabrik_transform_chain.write[final_joint_index] = final_bone2d_trans; + + int i = final_joint_index; + while (i >= 1) { + Transform2D previous_pose = fabrik_transform_chain[i]; + i -= 1; + Bone2D *current_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); + Transform2D current_pose = fabrik_transform_chain[i]; + + float current_bone2d_node_length = current_bone2d_node->get_length() * MIN(current_bone2d_node->get_global_scale().x, current_bone2d_node->get_global_scale().y); + float length = current_bone2d_node_length / (previous_pose.get_origin() - current_pose.get_origin()).length(); + Vector2 finish_position = previous_pose.get_origin().lerp(current_pose.get_origin(), length); + current_pose.set_origin(finish_position); + + // Save the transform + fabrik_transform_chain.write[i] = current_pose; + } +} + +void SkeletonModification2DFABRIK::chain_forwards() { + Transform2D origin_bone2d_trans = fabrik_transform_chain[0]; + origin_bone2d_trans.set_origin(origin_global_pose.get_origin()); + // Save the position + fabrik_transform_chain.write[0] = origin_bone2d_trans; + + for (int i = 0; i < fabrik_data_chain.size() - 1; i++) { + Bone2D *current_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); + Transform2D current_pose = fabrik_transform_chain[i]; + Transform2D next_pose = fabrik_transform_chain[i + 1]; + + float current_bone2d_node_length = current_bone2d_node->get_length() * MIN(current_bone2d_node->get_global_scale().x, current_bone2d_node->get_global_scale().y); + float length = current_bone2d_node_length / (current_pose.get_origin() - next_pose.get_origin()).length(); + Vector2 finish_position = current_pose.get_origin().lerp(next_pose.get_origin(), length); + current_pose.set_origin(finish_position); + + // Apply to the bone + fabrik_transform_chain.write[i + 1] = current_pose; + } +} + +void SkeletonModification2DFABRIK::_setup_modification(SkeletonModificationStack2D *p_stack) { + stack = p_stack; + + if (stack != nullptr) { + is_setup = true; + update_target_cache(); + } +} + +void SkeletonModification2DFABRIK::update_target_cache() { + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + return; + } + + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: node is not in scene tree!"); + target_node_cache = node->get_instance_id(); + } + } + } +} + +void SkeletonModification2DFABRIK::fabrik_joint_update_bone2d_cache(int p_joint_idx) { + ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "Cannot update bone2d cache: joint index out of range!"); + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update FABRIK Bone2D cache: modification is not properly setup!"); + return; + } + + fabrik_data_chain.write[p_joint_idx].bone2d_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(fabrik_data_chain[p_joint_idx].bone2d_node)) { + Node *node = stack->skeleton->get_node(fabrik_data_chain[p_joint_idx].bone2d_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update FABRIK joint " + itos(p_joint_idx) + " Bone2D cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update FABRIK joint " + itos(p_joint_idx) + " Bone2D cache: node is not in scene tree!"); + fabrik_data_chain.write[p_joint_idx].bone2d_node_cache = node->get_instance_id(); + + Bone2D *bone = Object::cast_to<Bone2D>(node); + if (bone) { + fabrik_data_chain.write[p_joint_idx].bone_idx = bone->get_index_in_skeleton(); + } else { + ERR_FAIL_MSG("FABRIK joint " + itos(p_joint_idx) + " Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); + } + } + } + } +} + +void SkeletonModification2DFABRIK::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_target_cache(); +} + +NodePath SkeletonModification2DFABRIK::get_target_node() const { + return target_node; +} + +void SkeletonModification2DFABRIK::set_fabrik_data_chain_length(int p_length) { + fabrik_data_chain.resize(p_length); + notify_property_list_changed(); +} + +int SkeletonModification2DFABRIK::get_fabrik_data_chain_length() { + return fabrik_data_chain.size(); +} + +void SkeletonModification2DFABRIK::set_fabrik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node) { + ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!"); + fabrik_data_chain.write[p_joint_idx].bone2d_node = p_target_node; + fabrik_joint_update_bone2d_cache(p_joint_idx); + + notify_property_list_changed(); +} + +NodePath SkeletonModification2DFABRIK::get_fabrik_joint_bone2d_node(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), NodePath(), "FABRIK joint out of range!"); + return fabrik_data_chain[p_joint_idx].bone2d_node; +} + +void SkeletonModification2DFABRIK::set_fabrik_joint_bone_index(int p_joint_idx, int p_bone_idx) { + ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!"); + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + + if (is_setup) { + if (stack->skeleton) { + ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); + fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; + fabrik_data_chain.write[p_joint_idx].bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); + fabrik_data_chain.write[p_joint_idx].bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); + } else { + WARN_PRINT("Cannot verify the FABRIK joint " + itos(p_joint_idx) + " bone index for this modification..."); + fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; + } + } else { + WARN_PRINT("Cannot verify the FABRIK joint " + itos(p_joint_idx) + " bone index for this modification..."); + fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; + } + + notify_property_list_changed(); +} + +int SkeletonModification2DFABRIK::get_fabrik_joint_bone_index(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), -1, "FABRIK joint out of range!"); + return fabrik_data_chain[p_joint_idx].bone_idx; +} + +void SkeletonModification2DFABRIK::set_fabrik_joint_magnet_position(int p_joint_idx, Vector2 p_magnet_position) { + ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!"); + fabrik_data_chain.write[p_joint_idx].magnet_position = p_magnet_position; +} + +Vector2 SkeletonModification2DFABRIK::get_fabrik_joint_magnet_position(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), Vector2(), "FABRIK joint out of range!"); + return fabrik_data_chain[p_joint_idx].magnet_position; +} + +void SkeletonModification2DFABRIK::set_fabrik_joint_use_target_rotation(int p_joint_idx, bool p_use_target_rotation) { + ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!"); + fabrik_data_chain.write[p_joint_idx].use_target_rotation = p_use_target_rotation; +} + +bool SkeletonModification2DFABRIK::get_fabrik_joint_use_target_rotation(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), false, "FABRIK joint out of range!"); + return fabrik_data_chain[p_joint_idx].use_target_rotation; +} + +void SkeletonModification2DFABRIK::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DFABRIK::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DFABRIK::get_target_node); + + ClassDB::bind_method(D_METHOD("set_fabrik_data_chain_length", "length"), &SkeletonModification2DFABRIK::set_fabrik_data_chain_length); + ClassDB::bind_method(D_METHOD("get_fabrik_data_chain_length"), &SkeletonModification2DFABRIK::get_fabrik_data_chain_length); + + ClassDB::bind_method(D_METHOD("set_fabrik_joint_bone2d_node", "joint_idx", "bone2d_nodepath"), &SkeletonModification2DFABRIK::set_fabrik_joint_bone2d_node); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_bone2d_node", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_bone2d_node); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification2DFABRIK::set_fabrik_joint_bone_index); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_bone_index", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_bone_index); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_magnet_position", "joint_idx", "magnet_position"), &SkeletonModification2DFABRIK::set_fabrik_joint_magnet_position); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_magnet_position", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_magnet_position); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_use_target_rotation", "joint_idx", "use_target_rotation"), &SkeletonModification2DFABRIK::set_fabrik_joint_use_target_rotation); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_use_target_rotation", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_use_target_rotation); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fabrik_data_chain_length", PROPERTY_HINT_RANGE, "0, 100, 1"), "set_fabrik_data_chain_length", "get_fabrik_data_chain_length"); +} + +SkeletonModification2DFABRIK::SkeletonModification2DFABRIK() { + stack = nullptr; + is_setup = false; + enabled = true; + editor_draw_gizmo = false; +} + +SkeletonModification2DFABRIK::~SkeletonModification2DFABRIK() { +} diff --git a/scene/resources/skeleton_modification_2d_fabrik.h b/scene/resources/skeleton_modification_2d_fabrik.h new file mode 100644 index 0000000000..79e0106e26 --- /dev/null +++ b/scene/resources/skeleton_modification_2d_fabrik.h @@ -0,0 +1,108 @@ +/*************************************************************************/ +/* skeleton_modification_2d_fabrik.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SKELETONMODIFICATION2DFABRIK_H +#define SKELETONMODIFICATION2DFABRIK_H + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/skeleton_modification_2d.h" + +/////////////////////////////////////// +// SkeletonModification2DFABRIK +/////////////////////////////////////// + +class SkeletonModification2DFABRIK : public SkeletonModification2D { + GDCLASS(SkeletonModification2DFABRIK, SkeletonModification2D); + +private: + struct FABRIK_Joint_Data2D { + int bone_idx = -1; + NodePath bone2d_node; + ObjectID bone2d_node_cache; + + Vector2 magnet_position = Vector2(0, 0); + bool use_target_rotation = false; + + bool editor_draw_gizmo = true; + }; + + Vector<FABRIK_Joint_Data2D> fabrik_data_chain; + + // Unlike in 3D, we need a vector of Transform2D objects to perform FABRIK. + // This is because FABRIK (unlike CCDIK) needs to operate on transforms that are NOT + // affected by each other, making the transforms stored in Bone2D unusable, as well as those in Skeleton2D. + // For this reason, this modification stores a vector of Transform2Ds used for the calculations, which are then applied at the end. + Vector<Transform2D> fabrik_transform_chain; + + NodePath target_node; + ObjectID target_node_cache; + void update_target_cache(); + + float chain_tolarance = 0.01; + int chain_max_iterations = 10; + int chain_iterations = 0; + Transform2D target_global_pose = Transform2D(); + Transform2D origin_global_pose = Transform2D(); + + void fabrik_joint_update_bone2d_cache(int p_joint_idx); + void chain_backwards(); + void chain_forwards(); + +protected: + static void _bind_methods(); + bool _set(const StringName &p_path, const Variant &p_value); + bool _get(const StringName &p_path, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + void _execute(float p_delta) override; + void _setup_modification(SkeletonModificationStack2D *p_stack) override; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + + int get_fabrik_data_chain_length(); + void set_fabrik_data_chain_length(int p_new_length); + + void set_fabrik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node); + NodePath get_fabrik_joint_bone2d_node(int p_joint_idx) const; + void set_fabrik_joint_bone_index(int p_joint_idx, int p_bone_idx); + int get_fabrik_joint_bone_index(int p_joint_idx) const; + + void set_fabrik_joint_magnet_position(int p_joint_idx, Vector2 p_magnet_position); + Vector2 get_fabrik_joint_magnet_position(int p_joint_idx) const; + void set_fabrik_joint_use_target_rotation(int p_joint_idx, bool p_use_target_rotation); + bool get_fabrik_joint_use_target_rotation(int p_joint_idx) const; + + SkeletonModification2DFABRIK(); + ~SkeletonModification2DFABRIK(); +}; + +#endif // SKELETONMODIFICATION2DFABRIK_H diff --git a/scene/resources/skeleton_modification_2d_jiggle.cpp b/scene/resources/skeleton_modification_2d_jiggle.cpp new file mode 100644 index 0000000000..2547083336 --- /dev/null +++ b/scene/resources/skeleton_modification_2d_jiggle.cpp @@ -0,0 +1,564 @@ +/*************************************************************************/ +/* skeleton_modification_2d_jiggle.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "skeleton_modification_2d_jiggle.h" +#include "scene/2d/skeleton_2d.h" + +bool SkeletonModification2DJiggle::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("joint_data/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, jiggle_data_chain.size(), false); + + if (what == "bone2d_node") { + set_jiggle_joint_bone2d_node(which, p_value); + } else if (what == "bone_index") { + set_jiggle_joint_bone_index(which, p_value); + } else if (what == "override_defaults") { + set_jiggle_joint_override(which, p_value); + } else if (what == "stiffness") { + set_jiggle_joint_stiffness(which, p_value); + } else if (what == "mass") { + set_jiggle_joint_mass(which, p_value); + } else if (what == "damping") { + set_jiggle_joint_damping(which, p_value); + } else if (what == "use_gravity") { + set_jiggle_joint_use_gravity(which, p_value); + } else if (what == "gravity") { + set_jiggle_joint_gravity(which, p_value); + } + return true; + } else { + if (path == "use_colliders") { + set_use_colliders(p_value); + } else if (path == "collision_mask") { + set_collision_mask(p_value); + } + } + return true; +} + +bool SkeletonModification2DJiggle::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("joint_data/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, jiggle_data_chain.size(), false); + + if (what == "bone2d_node") { + r_ret = get_jiggle_joint_bone2d_node(which); + } else if (what == "bone_index") { + r_ret = get_jiggle_joint_bone_index(which); + } else if (what == "override_defaults") { + r_ret = get_jiggle_joint_override(which); + } else if (what == "stiffness") { + r_ret = get_jiggle_joint_stiffness(which); + } else if (what == "mass") { + r_ret = get_jiggle_joint_mass(which); + } else if (what == "damping") { + r_ret = get_jiggle_joint_damping(which); + } else if (what == "use_gravity") { + r_ret = get_jiggle_joint_use_gravity(which); + } else if (what == "gravity") { + r_ret = get_jiggle_joint_gravity(which); + } + return true; + } else { + if (path == "use_colliders") { + r_ret = get_use_colliders(); + } else if (path == "collision_mask") { + r_ret = get_collision_mask(); + } + } + return true; +} + +void SkeletonModification2DJiggle::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::BOOL, "use_colliders", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (use_colliders) { + p_list->push_back(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS, "", PROPERTY_USAGE_DEFAULT)); + } + + for (int i = 0; i < jiggle_data_chain.size(); i++) { + String base_string = "joint_data/" + itos(i) + "/"; + + p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "override_defaults", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + + if (jiggle_data_chain[i].override_defaults) { + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "stiffness", PROPERTY_HINT_RANGE, "0, 1000, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "mass", PROPERTY_HINT_RANGE, "0, 1000, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "damping", PROPERTY_HINT_RANGE, "0, 1, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_gravity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (jiggle_data_chain[i].use_gravity) { + p_list->push_back(PropertyInfo(Variant::VECTOR2, base_string + "gravity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } + } + } +} + +void SkeletonModification2DJiggle::_execute(float p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + if (target_node_cache.is_null()) { + WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); + update_target_cache(); + return; + } + Node2D *target = Object::cast_to<Node2D>(ObjectDB::get_instance(target_node_cache)); + if (!target || !target->is_inside_tree()) { + ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); + return; + } + + for (int i = 0; i < jiggle_data_chain.size(); i++) { + _execute_jiggle_joint(i, target, p_delta); + } +} + +void SkeletonModification2DJiggle::_execute_jiggle_joint(int p_joint_idx, Node2D *p_target, float p_delta) { + // Adopted from: https://wiki.unity3d.com/index.php/JiggleBone + // With modifications by TwistedTwigleg. + + if (jiggle_data_chain[p_joint_idx].bone_idx <= -1 || jiggle_data_chain[p_joint_idx].bone_idx > stack->skeleton->get_bone_count()) { + ERR_PRINT_ONCE("Jiggle joint " + itos(p_joint_idx) + " bone index is invalid. Cannot execute modification on joint..."); + return; + } + + if (jiggle_data_chain[p_joint_idx].bone2d_node_cache.is_null() && !jiggle_data_chain[p_joint_idx].bone2d_node.is_empty()) { + WARN_PRINT_ONCE("Bone2D cache for joint " + itos(p_joint_idx) + " is out of date. Updating..."); + jiggle_joint_update_bone2d_cache(p_joint_idx); + } + + Bone2D *operation_bone = stack->skeleton->get_bone(jiggle_data_chain[p_joint_idx].bone_idx); + if (!operation_bone) { + ERR_PRINT_ONCE("Jiggle joint " + itos(p_joint_idx) + " does not have a Bone2D node or it cannot be found!"); + return; + } + + Transform2D operation_bone_trans = operation_bone->get_global_transform(); + Vector2 target_position = p_target->get_global_transform().get_origin(); + + jiggle_data_chain.write[p_joint_idx].force = (target_position - jiggle_data_chain[p_joint_idx].dynamic_position) * jiggle_data_chain[p_joint_idx].stiffness * p_delta; + + if (jiggle_data_chain[p_joint_idx].use_gravity) { + jiggle_data_chain.write[p_joint_idx].force += jiggle_data_chain[p_joint_idx].gravity * p_delta; + } + + jiggle_data_chain.write[p_joint_idx].acceleration = jiggle_data_chain[p_joint_idx].force / jiggle_data_chain[p_joint_idx].mass; + jiggle_data_chain.write[p_joint_idx].velocity += jiggle_data_chain[p_joint_idx].acceleration * (1 - jiggle_data_chain[p_joint_idx].damping); + + jiggle_data_chain.write[p_joint_idx].dynamic_position += jiggle_data_chain[p_joint_idx].velocity + jiggle_data_chain[p_joint_idx].force; + jiggle_data_chain.write[p_joint_idx].dynamic_position += operation_bone_trans.get_origin() - jiggle_data_chain[p_joint_idx].last_position; + jiggle_data_chain.write[p_joint_idx].last_position = operation_bone_trans.get_origin(); + + // Collision detection/response + if (use_colliders) { + if (execution_mode == SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_physics_process) { + Ref<World2D> world_2d = stack->skeleton->get_world_2d(); + ERR_FAIL_COND(world_2d.is_null()); + PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space()); + PhysicsDirectSpaceState2D::RayResult ray_result; + + // Add exception support? + bool ray_hit = space_state->intersect_ray(operation_bone_trans.get_origin(), jiggle_data_chain[p_joint_idx].dynamic_position, + ray_result, Set<RID>(), collision_mask); + + if (ray_hit) { + jiggle_data_chain.write[p_joint_idx].dynamic_position = jiggle_data_chain[p_joint_idx].last_noncollision_position; + jiggle_data_chain.write[p_joint_idx].acceleration = Vector2(0, 0); + jiggle_data_chain.write[p_joint_idx].velocity = Vector2(0, 0); + } else { + jiggle_data_chain.write[p_joint_idx].last_noncollision_position = jiggle_data_chain[p_joint_idx].dynamic_position; + } + } else { + WARN_PRINT_ONCE("Jiggle 2D modifier: You cannot detect colliders without the stack mode being set to _physics_process!"); + } + } + + // Rotate the bone using the dynamic position! + operation_bone_trans = operation_bone_trans.looking_at(jiggle_data_chain[p_joint_idx].dynamic_position); + operation_bone_trans.set_rotation(operation_bone_trans.get_rotation() - operation_bone->get_bone_angle()); + + // Reset scale + operation_bone_trans.set_scale(operation_bone->get_global_transform().get_scale()); + + operation_bone->set_global_transform(operation_bone_trans); + stack->skeleton->set_bone_local_pose_override(jiggle_data_chain[p_joint_idx].bone_idx, operation_bone->get_transform(), stack->strength, true); +} + +void SkeletonModification2DJiggle::_update_jiggle_joint_data() { + for (int i = 0; i < jiggle_data_chain.size(); i++) { + if (!jiggle_data_chain[i].override_defaults) { + set_jiggle_joint_stiffness(i, stiffness); + set_jiggle_joint_mass(i, mass); + set_jiggle_joint_damping(i, damping); + set_jiggle_joint_use_gravity(i, use_gravity); + set_jiggle_joint_gravity(i, gravity); + } + } +} + +void SkeletonModification2DJiggle::_setup_modification(SkeletonModificationStack2D *p_stack) { + stack = p_stack; + + if (stack) { + is_setup = true; + + if (stack->skeleton) { + for (int i = 0; i < jiggle_data_chain.size(); i++) { + int bone_idx = jiggle_data_chain[i].bone_idx; + if (bone_idx > 0 && bone_idx < stack->skeleton->get_bone_count()) { + Bone2D *bone2d_node = stack->skeleton->get_bone(bone_idx); + jiggle_data_chain.write[i].dynamic_position = bone2d_node->get_global_transform().get_origin(); + } + } + } + + update_target_cache(); + } +} + +void SkeletonModification2DJiggle::update_target_cache() { + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + return; + } + + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: node is not in scene tree!"); + target_node_cache = node->get_instance_id(); + } + } + } +} + +void SkeletonModification2DJiggle::jiggle_joint_update_bone2d_cache(int p_joint_idx) { + ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Cannot update bone2d cache: joint index out of range!"); + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update Jiggle " + itos(p_joint_idx) + " Bone2D cache: modification is not properly setup!"); + return; + } + + jiggle_data_chain.write[p_joint_idx].bone2d_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(jiggle_data_chain[p_joint_idx].bone2d_node)) { + Node *node = stack->skeleton->get_node(jiggle_data_chain[p_joint_idx].bone2d_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update Jiggle joint " + itos(p_joint_idx) + " Bone2D cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update Jiggle joint " + itos(p_joint_idx) + " Bone2D cache: node is not in scene tree!"); + jiggle_data_chain.write[p_joint_idx].bone2d_node_cache = node->get_instance_id(); + + Bone2D *bone = Object::cast_to<Bone2D>(node); + if (bone) { + jiggle_data_chain.write[p_joint_idx].bone_idx = bone->get_index_in_skeleton(); + } else { + ERR_FAIL_MSG("Jiggle joint " + itos(p_joint_idx) + " Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); + } + } + } + } +} + +void SkeletonModification2DJiggle::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_target_cache(); +} + +NodePath SkeletonModification2DJiggle::get_target_node() const { + return target_node; +} + +void SkeletonModification2DJiggle::set_stiffness(float p_stiffness) { + ERR_FAIL_COND_MSG(p_stiffness < 0, "Stiffness cannot be set to a negative value!"); + stiffness = p_stiffness; + _update_jiggle_joint_data(); +} + +float SkeletonModification2DJiggle::get_stiffness() const { + return stiffness; +} + +void SkeletonModification2DJiggle::set_mass(float p_mass) { + ERR_FAIL_COND_MSG(p_mass < 0, "Mass cannot be set to a negative value!"); + mass = p_mass; + _update_jiggle_joint_data(); +} + +float SkeletonModification2DJiggle::get_mass() const { + return mass; +} + +void SkeletonModification2DJiggle::set_damping(float p_damping) { + ERR_FAIL_COND_MSG(p_damping < 0, "Damping cannot be set to a negative value!"); + ERR_FAIL_COND_MSG(p_damping > 1, "Damping cannot be more than one!"); + damping = p_damping; + _update_jiggle_joint_data(); +} + +float SkeletonModification2DJiggle::get_damping() const { + return damping; +} + +void SkeletonModification2DJiggle::set_use_gravity(bool p_use_gravity) { + use_gravity = p_use_gravity; + _update_jiggle_joint_data(); +} + +bool SkeletonModification2DJiggle::get_use_gravity() const { + return use_gravity; +} + +void SkeletonModification2DJiggle::set_gravity(Vector2 p_gravity) { + gravity = p_gravity; + _update_jiggle_joint_data(); +} + +Vector2 SkeletonModification2DJiggle::get_gravity() const { + return gravity; +} + +void SkeletonModification2DJiggle::set_use_colliders(bool p_use_colliders) { + use_colliders = p_use_colliders; + notify_property_list_changed(); +} + +bool SkeletonModification2DJiggle::get_use_colliders() const { + return use_colliders; +} + +void SkeletonModification2DJiggle::set_collision_mask(int p_mask) { + collision_mask = p_mask; +} + +int SkeletonModification2DJiggle::get_collision_mask() const { + return collision_mask; +} + +// Jiggle joint data functions +int SkeletonModification2DJiggle::get_jiggle_data_chain_length() { + return jiggle_data_chain.size(); +} + +void SkeletonModification2DJiggle::set_jiggle_data_chain_length(int p_length) { + ERR_FAIL_COND(p_length < 0); + jiggle_data_chain.resize(p_length); + notify_property_list_changed(); +} + +void SkeletonModification2DJiggle::set_jiggle_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node) { + ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Jiggle joint out of range!"); + jiggle_data_chain.write[p_joint_idx].bone2d_node = p_target_node; + jiggle_joint_update_bone2d_cache(p_joint_idx); + + notify_property_list_changed(); +} + +NodePath SkeletonModification2DJiggle::get_jiggle_joint_bone2d_node(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, jiggle_data_chain.size(), NodePath(), "Jiggle joint out of range!"); + return jiggle_data_chain[p_joint_idx].bone2d_node; +} + +void SkeletonModification2DJiggle::set_jiggle_joint_bone_index(int p_joint_idx, int p_bone_idx) { + ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Jiggle joint out of range!"); + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + + if (is_setup) { + if (stack->skeleton) { + ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); + jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; + jiggle_data_chain.write[p_joint_idx].bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); + jiggle_data_chain.write[p_joint_idx].bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); + } else { + WARN_PRINT("Cannot verify the Jiggle joint " + itos(p_joint_idx) + " bone index for this modification..."); + jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; + } + } else { + WARN_PRINT("Cannot verify the Jiggle joint " + itos(p_joint_idx) + " bone index for this modification..."); + jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; + } + + notify_property_list_changed(); +} + +int SkeletonModification2DJiggle::get_jiggle_joint_bone_index(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, jiggle_data_chain.size(), -1, "Jiggle joint out of range!"); + return jiggle_data_chain[p_joint_idx].bone_idx; +} + +void SkeletonModification2DJiggle::set_jiggle_joint_override(int p_joint_idx, bool p_override) { + ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); + jiggle_data_chain.write[p_joint_idx].override_defaults = p_override; + _update_jiggle_joint_data(); + notify_property_list_changed(); +} + +bool SkeletonModification2DJiggle::get_jiggle_joint_override(int p_joint_idx) const { + ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), false); + return jiggle_data_chain[p_joint_idx].override_defaults; +} + +void SkeletonModification2DJiggle::set_jiggle_joint_stiffness(int p_joint_idx, float p_stiffness) { + ERR_FAIL_COND_MSG(p_stiffness < 0, "Stiffness cannot be set to a negative value!"); + ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); + jiggle_data_chain.write[p_joint_idx].stiffness = p_stiffness; +} + +float SkeletonModification2DJiggle::get_jiggle_joint_stiffness(int p_joint_idx) const { + ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), -1); + return jiggle_data_chain[p_joint_idx].stiffness; +} + +void SkeletonModification2DJiggle::set_jiggle_joint_mass(int p_joint_idx, float p_mass) { + ERR_FAIL_COND_MSG(p_mass < 0, "Mass cannot be set to a negative value!"); + ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); + jiggle_data_chain.write[p_joint_idx].mass = p_mass; +} + +float SkeletonModification2DJiggle::get_jiggle_joint_mass(int p_joint_idx) const { + ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), -1); + return jiggle_data_chain[p_joint_idx].mass; +} + +void SkeletonModification2DJiggle::set_jiggle_joint_damping(int p_joint_idx, float p_damping) { + ERR_FAIL_COND_MSG(p_damping < 0, "Damping cannot be set to a negative value!"); + ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); + jiggle_data_chain.write[p_joint_idx].damping = p_damping; +} + +float SkeletonModification2DJiggle::get_jiggle_joint_damping(int p_joint_idx) const { + ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), -1); + return jiggle_data_chain[p_joint_idx].damping; +} + +void SkeletonModification2DJiggle::set_jiggle_joint_use_gravity(int p_joint_idx, bool p_use_gravity) { + ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); + jiggle_data_chain.write[p_joint_idx].use_gravity = p_use_gravity; + notify_property_list_changed(); +} + +bool SkeletonModification2DJiggle::get_jiggle_joint_use_gravity(int p_joint_idx) const { + ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), false); + return jiggle_data_chain[p_joint_idx].use_gravity; +} + +void SkeletonModification2DJiggle::set_jiggle_joint_gravity(int p_joint_idx, Vector2 p_gravity) { + ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); + jiggle_data_chain.write[p_joint_idx].gravity = p_gravity; +} + +Vector2 SkeletonModification2DJiggle::get_jiggle_joint_gravity(int p_joint_idx) const { + ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), Vector2(0, 0)); + return jiggle_data_chain[p_joint_idx].gravity; +} + +void SkeletonModification2DJiggle::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DJiggle::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DJiggle::get_target_node); + + ClassDB::bind_method(D_METHOD("set_jiggle_data_chain_length", "length"), &SkeletonModification2DJiggle::set_jiggle_data_chain_length); + ClassDB::bind_method(D_METHOD("get_jiggle_data_chain_length"), &SkeletonModification2DJiggle::get_jiggle_data_chain_length); + + ClassDB::bind_method(D_METHOD("set_stiffness", "stiffness"), &SkeletonModification2DJiggle::set_stiffness); + ClassDB::bind_method(D_METHOD("get_stiffness"), &SkeletonModification2DJiggle::get_stiffness); + ClassDB::bind_method(D_METHOD("set_mass", "mass"), &SkeletonModification2DJiggle::set_mass); + ClassDB::bind_method(D_METHOD("get_mass"), &SkeletonModification2DJiggle::get_mass); + ClassDB::bind_method(D_METHOD("set_damping", "damping"), &SkeletonModification2DJiggle::set_damping); + ClassDB::bind_method(D_METHOD("get_damping"), &SkeletonModification2DJiggle::get_damping); + ClassDB::bind_method(D_METHOD("set_use_gravity", "use_gravity"), &SkeletonModification2DJiggle::set_use_gravity); + ClassDB::bind_method(D_METHOD("get_use_gravity"), &SkeletonModification2DJiggle::get_use_gravity); + ClassDB::bind_method(D_METHOD("set_gravity", "gravity"), &SkeletonModification2DJiggle::set_gravity); + ClassDB::bind_method(D_METHOD("get_gravity"), &SkeletonModification2DJiggle::get_gravity); + + ClassDB::bind_method(D_METHOD("set_use_colliders", "use_colliders"), &SkeletonModification2DJiggle::set_use_colliders); + ClassDB::bind_method(D_METHOD("get_use_colliders"), &SkeletonModification2DJiggle::get_use_colliders); + ClassDB::bind_method(D_METHOD("set_collision_mask", "collision_mask"), &SkeletonModification2DJiggle::set_collision_mask); + ClassDB::bind_method(D_METHOD("get_collision_mask"), &SkeletonModification2DJiggle::get_collision_mask); + + // Jiggle joint data functions + ClassDB::bind_method(D_METHOD("set_jiggle_joint_bone2d_node", "joint_idx", "bone2d_node"), &SkeletonModification2DJiggle::set_jiggle_joint_bone2d_node); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_bone2d_node", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_bone2d_node); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification2DJiggle::set_jiggle_joint_bone_index); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_bone_index", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_bone_index); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_override", "joint_idx", "override"), &SkeletonModification2DJiggle::set_jiggle_joint_override); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_override", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_override); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_stiffness", "joint_idx", "stiffness"), &SkeletonModification2DJiggle::set_jiggle_joint_stiffness); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_stiffness", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_stiffness); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_mass", "joint_idx", "mass"), &SkeletonModification2DJiggle::set_jiggle_joint_mass); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_mass", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_mass); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_damping", "joint_idx", "damping"), &SkeletonModification2DJiggle::set_jiggle_joint_damping); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_damping", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_damping); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_use_gravity", "joint_idx", "use_gravity"), &SkeletonModification2DJiggle::set_jiggle_joint_use_gravity); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_use_gravity", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_use_gravity); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_gravity", "joint_idx", "gravity"), &SkeletonModification2DJiggle::set_jiggle_joint_gravity); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_gravity", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_gravity); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "jiggle_data_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_jiggle_data_chain_length", "get_jiggle_data_chain_length"); + ADD_GROUP("Default Joint Settings", ""); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "stiffness"), "set_stiffness", "get_stiffness"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass"), "set_mass", "get_mass"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0, 1, 0.01"), "set_damping", "get_damping"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_gravity"), "set_use_gravity", "get_use_gravity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity"), "set_gravity", "get_gravity"); + ADD_GROUP("", ""); +} + +SkeletonModification2DJiggle::SkeletonModification2DJiggle() { + stack = nullptr; + is_setup = false; + jiggle_data_chain = Vector<Jiggle_Joint_Data2D>(); + stiffness = 3; + mass = 0.75; + damping = 0.75; + use_gravity = false; + gravity = Vector2(0, 6.0); + enabled = true; + editor_draw_gizmo = false; // Nothing to really show in a gizmo right now. +} + +SkeletonModification2DJiggle::~SkeletonModification2DJiggle() { +} diff --git a/scene/resources/skeleton_modification_2d_jiggle.h b/scene/resources/skeleton_modification_2d_jiggle.h new file mode 100644 index 0000000000..e24038a1db --- /dev/null +++ b/scene/resources/skeleton_modification_2d_jiggle.h @@ -0,0 +1,139 @@ +/*************************************************************************/ +/* skeleton_modification_2d_jiggle.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SKELETONMODIFICATION2DJIGGLE_H +#define SKELETONMODIFICATION2DJIGGLE_H + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/skeleton_modification_2d.h" + +/////////////////////////////////////// +// SkeletonModification2DJIGGLE +/////////////////////////////////////// + +class SkeletonModification2DJiggle : public SkeletonModification2D { + GDCLASS(SkeletonModification2DJiggle, SkeletonModification2D); + +private: + struct Jiggle_Joint_Data2D { + int bone_idx = -1; + NodePath bone2d_node; + ObjectID bone2d_node_cache; + + bool override_defaults = false; + float stiffness = 3; + float mass = 0.75; + float damping = 0.75; + bool use_gravity = false; + Vector2 gravity = Vector2(0, 6.0); + + Vector2 force = Vector2(0, 0); + Vector2 acceleration = Vector2(0, 0); + Vector2 velocity = Vector2(0, 0); + Vector2 last_position = Vector2(0, 0); + Vector2 dynamic_position = Vector2(0, 0); + + Vector2 last_noncollision_position = Vector2(0, 0); + }; + + Vector<Jiggle_Joint_Data2D> jiggle_data_chain; + + NodePath target_node; + ObjectID target_node_cache; + void update_target_cache(); + + float stiffness = 3; + float mass = 0.75; + float damping = 0.75; + bool use_gravity = false; + Vector2 gravity = Vector2(0, 6); + + bool use_colliders = false; + uint32_t collision_mask = 1; + + void jiggle_joint_update_bone2d_cache(int p_joint_idx); + void _execute_jiggle_joint(int p_joint_idx, Node2D *p_target, float p_delta); + void _update_jiggle_joint_data(); + +protected: + static void _bind_methods(); + bool _set(const StringName &p_path, const Variant &p_value); + bool _get(const StringName &p_path, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + void _execute(float p_delta) override; + void _setup_modification(SkeletonModificationStack2D *p_stack) override; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + + void set_stiffness(float p_stiffness); + float get_stiffness() const; + void set_mass(float p_mass); + float get_mass() const; + void set_damping(float p_damping); + float get_damping() const; + void set_use_gravity(bool p_use_gravity); + bool get_use_gravity() const; + void set_gravity(Vector2 p_gravity); + Vector2 get_gravity() const; + + void set_use_colliders(bool p_use_colliders); + bool get_use_colliders() const; + void set_collision_mask(int p_mask); + int get_collision_mask() const; + + int get_jiggle_data_chain_length(); + void set_jiggle_data_chain_length(int p_new_length); + + void set_jiggle_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node); + NodePath get_jiggle_joint_bone2d_node(int p_joint_idx) const; + void set_jiggle_joint_bone_index(int p_joint_idx, int p_bone_idx); + int get_jiggle_joint_bone_index(int p_joint_idx) const; + + void set_jiggle_joint_override(int p_joint_idx, bool p_override); + bool get_jiggle_joint_override(int p_joint_idx) const; + void set_jiggle_joint_stiffness(int p_joint_idx, float p_stiffness); + float get_jiggle_joint_stiffness(int p_joint_idx) const; + void set_jiggle_joint_mass(int p_joint_idx, float p_mass); + float get_jiggle_joint_mass(int p_joint_idx) const; + void set_jiggle_joint_damping(int p_joint_idx, float p_damping); + float get_jiggle_joint_damping(int p_joint_idx) const; + void set_jiggle_joint_use_gravity(int p_joint_idx, bool p_use_gravity); + bool get_jiggle_joint_use_gravity(int p_joint_idx) const; + void set_jiggle_joint_gravity(int p_joint_idx, Vector2 p_gravity); + Vector2 get_jiggle_joint_gravity(int p_joint_idx) const; + + SkeletonModification2DJiggle(); + ~SkeletonModification2DJiggle(); +}; + +#endif // SKELETONMODIFICATION2DJIGGLE_H diff --git a/scene/resources/skeleton_modification_2d_lookat.cpp b/scene/resources/skeleton_modification_2d_lookat.cpp new file mode 100644 index 0000000000..fd5c8c7cc2 --- /dev/null +++ b/scene/resources/skeleton_modification_2d_lookat.cpp @@ -0,0 +1,407 @@ +/*************************************************************************/ +/* skeleton_modification_2d_lookat.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "skeleton_modification_2d_lookat.h" +#include "scene/2d/skeleton_2d.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif // TOOLS_ENABLED + +bool SkeletonModification2DLookAt::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("enable_constraint")) { + set_enable_constraint(p_value); + } else if (path.begins_with("constraint_angle_min")) { + set_constraint_angle_min(Math::deg2rad(float(p_value))); + } else if (path.begins_with("constraint_angle_max")) { + set_constraint_angle_max(Math::deg2rad(float(p_value))); + } else if (path.begins_with("constraint_angle_invert")) { + set_constraint_angle_invert(p_value); + } else if (path.begins_with("constraint_in_localspace")) { + set_constraint_in_localspace(p_value); + } else if (path.begins_with("additional_rotation")) { + set_additional_rotation(Math::deg2rad(float(p_value))); + } + +#ifdef TOOLS_ENABLED + if (path.begins_with("editor/draw_gizmo")) { + set_editor_draw_gizmo(p_value); + } +#endif // TOOLS_ENABLED + + return true; +} + +bool SkeletonModification2DLookAt::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("enable_constraint")) { + r_ret = get_enable_constraint(); + } else if (path.begins_with("constraint_angle_min")) { + r_ret = Math::rad2deg(get_constraint_angle_min()); + } else if (path.begins_with("constraint_angle_max")) { + r_ret = Math::rad2deg(get_constraint_angle_max()); + } else if (path.begins_with("constraint_angle_invert")) { + r_ret = get_constraint_angle_invert(); + } else if (path.begins_with("constraint_in_localspace")) { + r_ret = get_constraint_in_localspace(); + } else if (path.begins_with("additional_rotation")) { + r_ret = Math::rad2deg(get_additional_rotation()); + } + +#ifdef TOOLS_ENABLED + if (path.begins_with("editor/draw_gizmo")) { + r_ret = get_editor_draw_gizmo(); + } +#endif // TOOLS_ENABLED + + return true; +} + +void SkeletonModification2DLookAt::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::BOOL, "enable_constraint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (enable_constraint) { + p_list->push_back(PropertyInfo(Variant::FLOAT, "constraint_angle_min", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, "constraint_angle_max", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, "constraint_angle_invert", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, "constraint_in_localspace", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } + p_list->push_back(PropertyInfo(Variant::FLOAT, "additional_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } +#endif // TOOLS_ENABLED +} + +void SkeletonModification2DLookAt::_execute(float p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + + if (target_node_cache.is_null()) { + WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); + update_target_cache(); + return; + } + + if (bone2d_node_cache.is_null() && !bone2d_node.is_empty()) { + update_bone2d_cache(); + WARN_PRINT_ONCE("Bone2D node cache is out of date. Attempting to update..."); + return; + } + + if (target_node_reference == nullptr) { + target_node_reference = Object::cast_to<Node2D>(ObjectDB::get_instance(target_node_cache)); + } + if (!target_node_reference || !target_node_reference->is_inside_tree()) { + ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); + return; + } + if (bone_idx <= -1) { + ERR_PRINT_ONCE("Bone index is invalid. Cannot execute modification!"); + return; + } + + Bone2D *operation_bone = stack->skeleton->get_bone(bone_idx); + if (operation_bone == nullptr) { + ERR_PRINT_ONCE("bone_idx for modification does not point to a valid bone! Cannot execute modification"); + return; + } + + Transform2D operation_transform = operation_bone->get_global_transform(); + Transform2D target_trans = target_node_reference->get_global_transform(); + + // Look at the target! + operation_transform = operation_transform.looking_at(target_trans.get_origin()); + // Apply whatever scale it had prior to looking_at + operation_transform.set_scale(operation_bone->get_global_transform().get_scale()); + + // Account for the direction the bone faces in: + operation_transform.set_rotation(operation_transform.get_rotation() - operation_bone->get_bone_angle()); + + // Apply additional rotation + operation_transform.set_rotation(operation_transform.get_rotation() + additional_rotation); + + // Apply constraints in globalspace: + if (enable_constraint && !constraint_in_localspace) { + operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), constraint_angle_min, constraint_angle_max, constraint_angle_invert)); + } + + // Convert from a global transform to a local transform via the Bone2D node + operation_bone->set_global_transform(operation_transform); + operation_transform = operation_bone->get_transform(); + + // Apply constraints in localspace: + if (enable_constraint && constraint_in_localspace) { + operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), constraint_angle_min, constraint_angle_max, constraint_angle_invert)); + } + + // Set the local pose override, and to make sure child bones are also updated, set the transform of the bone. + stack->skeleton->set_bone_local_pose_override(bone_idx, operation_transform, stack->strength, true); + operation_bone->set_transform(operation_transform); +} + +void SkeletonModification2DLookAt::_setup_modification(SkeletonModificationStack2D *p_stack) { + stack = p_stack; + + if (stack != nullptr) { + is_setup = true; + update_target_cache(); + update_bone2d_cache(); + } +} + +void SkeletonModification2DLookAt::_draw_editor_gizmo() { + if (!enabled || !is_setup) { + return; + } + + Bone2D *operation_bone = stack->skeleton->get_bone(bone_idx); + editor_draw_angle_constraints(operation_bone, constraint_angle_min, constraint_angle_max, + enable_constraint, constraint_in_localspace, constraint_angle_invert); +} + +void SkeletonModification2DLookAt::update_bone2d_cache() { + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update Bone2D cache: modification is not properly setup!"); + return; + } + + bone2d_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(bone2d_node)) { + Node *node = stack->skeleton->get_node(bone2d_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update Bone2D cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update Bone2D cache: node is not in the scene tree!"); + bone2d_node_cache = node->get_instance_id(); + + Bone2D *bone = Object::cast_to<Bone2D>(node); + if (bone) { + bone_idx = bone->get_index_in_skeleton(); + } else { + ERR_FAIL_MSG("Error Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); + } + + // Set this to null so we update it + target_node_reference = nullptr; + } + } + } +} + +void SkeletonModification2DLookAt::set_bone2d_node(const NodePath &p_target_node) { + bone2d_node = p_target_node; + update_bone2d_cache(); +} + +NodePath SkeletonModification2DLookAt::get_bone2d_node() const { + return bone2d_node; +} + +int SkeletonModification2DLookAt::get_bone_index() const { + return bone_idx; +} + +void SkeletonModification2DLookAt::set_bone_index(int p_bone_idx) { + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + + if (is_setup) { + if (stack->skeleton) { + ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); + bone_idx = p_bone_idx; + bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); + bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); + } else { + WARN_PRINT("Cannot verify the bone index for this modification..."); + bone_idx = p_bone_idx; + } + } else { + WARN_PRINT("Cannot verify the bone index for this modification..."); + bone_idx = p_bone_idx; + } + + notify_property_list_changed(); +} + +void SkeletonModification2DLookAt::update_target_cache() { + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + return; + } + + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: node is not in the scene tree!"); + target_node_cache = node->get_instance_id(); + } + } + } +} + +void SkeletonModification2DLookAt::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_target_cache(); +} + +NodePath SkeletonModification2DLookAt::get_target_node() const { + return target_node; +} + +float SkeletonModification2DLookAt::get_additional_rotation() const { + return additional_rotation; +} + +void SkeletonModification2DLookAt::set_additional_rotation(float p_rotation) { + additional_rotation = p_rotation; +} + +void SkeletonModification2DLookAt::set_enable_constraint(bool p_constraint) { + enable_constraint = p_constraint; + notify_property_list_changed(); +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2DLookAt::get_enable_constraint() const { + return enable_constraint; +} + +void SkeletonModification2DLookAt::set_constraint_angle_min(float p_angle_min) { + constraint_angle_min = p_angle_min; +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +float SkeletonModification2DLookAt::get_constraint_angle_min() const { + return constraint_angle_min; +} + +void SkeletonModification2DLookAt::set_constraint_angle_max(float p_angle_max) { + constraint_angle_max = p_angle_max; +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +float SkeletonModification2DLookAt::get_constraint_angle_max() const { + return constraint_angle_max; +} + +void SkeletonModification2DLookAt::set_constraint_angle_invert(bool p_invert) { + constraint_angle_invert = p_invert; +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2DLookAt::get_constraint_angle_invert() const { + return constraint_angle_invert; +} + +void SkeletonModification2DLookAt::set_constraint_in_localspace(bool p_constraint_in_localspace) { + constraint_in_localspace = p_constraint_in_localspace; +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2DLookAt::get_constraint_in_localspace() const { + return constraint_in_localspace; +} + +void SkeletonModification2DLookAt::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_bone2d_node", "bone2d_nodepath"), &SkeletonModification2DLookAt::set_bone2d_node); + ClassDB::bind_method(D_METHOD("get_bone2d_node"), &SkeletonModification2DLookAt::get_bone2d_node); + ClassDB::bind_method(D_METHOD("set_bone_index", "bone_idx"), &SkeletonModification2DLookAt::set_bone_index); + ClassDB::bind_method(D_METHOD("get_bone_index"), &SkeletonModification2DLookAt::get_bone_index); + + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DLookAt::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DLookAt::get_target_node); + + ClassDB::bind_method(D_METHOD("set_additional_rotation", "rotation"), &SkeletonModification2DLookAt::set_additional_rotation); + ClassDB::bind_method(D_METHOD("get_additional_rotation"), &SkeletonModification2DLookAt::get_additional_rotation); + + ClassDB::bind_method(D_METHOD("set_enable_constraint", "enable_constraint"), &SkeletonModification2DLookAt::set_enable_constraint); + ClassDB::bind_method(D_METHOD("get_enable_constraint"), &SkeletonModification2DLookAt::get_enable_constraint); + ClassDB::bind_method(D_METHOD("set_constraint_angle_min", "angle_min"), &SkeletonModification2DLookAt::set_constraint_angle_min); + ClassDB::bind_method(D_METHOD("get_constraint_angle_min"), &SkeletonModification2DLookAt::get_constraint_angle_min); + ClassDB::bind_method(D_METHOD("set_constraint_angle_max", "angle_max"), &SkeletonModification2DLookAt::set_constraint_angle_max); + ClassDB::bind_method(D_METHOD("get_constraint_angle_max"), &SkeletonModification2DLookAt::get_constraint_angle_max); + ClassDB::bind_method(D_METHOD("set_constraint_angle_invert", "invert"), &SkeletonModification2DLookAt::set_constraint_angle_invert); + ClassDB::bind_method(D_METHOD("get_constraint_angle_invert"), &SkeletonModification2DLookAt::get_constraint_angle_invert); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_index"), "set_bone_index", "get_bone_index"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D"), "set_bone2d_node", "get_bone2d_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); +} + +SkeletonModification2DLookAt::SkeletonModification2DLookAt() { + stack = nullptr; + is_setup = false; + bone_idx = -1; + additional_rotation = 0; + enable_constraint = false; + constraint_angle_min = 0; + constraint_angle_max = Math_PI * 2; + constraint_angle_invert = false; + enabled = true; + + editor_draw_gizmo = true; +} + +SkeletonModification2DLookAt::~SkeletonModification2DLookAt() { +} diff --git a/scene/resources/skeleton_modification_2d_lookat.h b/scene/resources/skeleton_modification_2d_lookat.h new file mode 100644 index 0000000000..6aff30b826 --- /dev/null +++ b/scene/resources/skeleton_modification_2d_lookat.h @@ -0,0 +1,100 @@ +/*************************************************************************/ +/* skeleton_modification_2d_lookat.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SKELETONMODIFICATION2DLOOKAT_H +#define SKELETONMODIFICATION2DLOOKAT_H + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/skeleton_modification_2d.h" + +/////////////////////////////////////// +// SkeletonModification2DLookAt +/////////////////////////////////////// + +class SkeletonModification2DLookAt : public SkeletonModification2D { + GDCLASS(SkeletonModification2DLookAt, SkeletonModification2D); + +private: + int bone_idx = -1; + NodePath bone2d_node; + ObjectID bone2d_node_cache; + + NodePath target_node; + ObjectID target_node_cache; + Node2D *target_node_reference = nullptr; + + float additional_rotation = 0; + bool enable_constraint = false; + float constraint_angle_min = 0; + float constraint_angle_max = (2.0 * Math_PI); + bool constraint_angle_invert = false; + bool constraint_in_localspace = true; + + void update_bone2d_cache(); + void update_target_cache(); + +protected: + static void _bind_methods(); + bool _set(const StringName &p_path, const Variant &p_value); + bool _get(const StringName &p_path, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + void _execute(float p_delta) override; + void _setup_modification(SkeletonModificationStack2D *p_stack) override; + void _draw_editor_gizmo() override; + + void set_bone2d_node(const NodePath &p_target_node); + NodePath get_bone2d_node() const; + void set_bone_index(int p_idx); + int get_bone_index() const; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + + void set_additional_rotation(float p_rotation); + float get_additional_rotation() const; + + void set_enable_constraint(bool p_constraint); + bool get_enable_constraint() const; + void set_constraint_angle_min(float p_angle_min); + float get_constraint_angle_min() const; + void set_constraint_angle_max(float p_angle_max); + float get_constraint_angle_max() const; + void set_constraint_angle_invert(bool p_invert); + bool get_constraint_angle_invert() const; + void set_constraint_in_localspace(bool p_constraint_in_localspace); + bool get_constraint_in_localspace() const; + + SkeletonModification2DLookAt(); + ~SkeletonModification2DLookAt(); +}; + +#endif // SKELETONMODIFICATION2DLOOKAT_H diff --git a/scene/resources/skeleton_modification_2d_physicalbones.cpp b/scene/resources/skeleton_modification_2d_physicalbones.cpp new file mode 100644 index 0000000000..9dedb93f36 --- /dev/null +++ b/scene/resources/skeleton_modification_2d_physicalbones.cpp @@ -0,0 +1,297 @@ +/*************************************************************************/ +/* skeleton_modification_2d_physicalbones.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "skeleton_modification_2d_physicalbones.h" +#include "scene/2d/physical_bone_2d.h" +#include "scene/2d/skeleton_2d.h" + +bool SkeletonModification2DPhysicalBones::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + +#ifdef TOOLS_ENABLED + // Exposes a way to fetch the PhysicalBone2D nodes from the Godot editor. + if (is_setup) { + if (Engine::get_singleton()->is_editor_hint()) { + if (path.begins_with("fetch_bones")) { + fetch_physical_bones(); + notify_property_list_changed(); + return true; + } + } + } +#endif //TOOLS_ENABLED + + if (path.begins_with("joint_")) { + int which = path.get_slicec('_', 1).to_int(); + String what = path.get_slicec('_', 2); + ERR_FAIL_INDEX_V(which, physical_bone_chain.size(), false); + + if (what == "nodepath") { + set_physical_bone_node(which, p_value); + } + return true; + } + return true; +} + +bool SkeletonModification2DPhysicalBones::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + if (path.begins_with("fetch_bones")) { + return true; // Do nothing! + } + } +#endif //TOOLS_ENABLED + + if (path.begins_with("joint_")) { + int which = path.get_slicec('_', 1).to_int(); + String what = path.get_slicec('_', 2); + ERR_FAIL_INDEX_V(which, physical_bone_chain.size(), false); + + if (what == "nodepath") { + r_ret = get_physical_bone_node(which); + } + return true; + } + return true; +} + +void SkeletonModification2DPhysicalBones::_get_property_list(List<PropertyInfo> *p_list) const { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + p_list->push_back(PropertyInfo(Variant::BOOL, "fetch_bones", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } +#endif //TOOLS_ENABLED + + for (int i = 0; i < physical_bone_chain.size(); i++) { + String base_string = "joint_" + itos(i) + "_"; + + p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "PhysicalBone2D", PROPERTY_USAGE_DEFAULT)); + } +} + +void SkeletonModification2DPhysicalBones::_execute(float p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + + if (_simulation_state_dirty) { + _update_simulation_state(); + } + + for (int i = 0; i < physical_bone_chain.size(); i++) { + PhysicalBone_Data2D bone_data = physical_bone_chain[i]; + if (bone_data.physical_bone_node_cache.is_null()) { + WARN_PRINT_ONCE("PhysicalBone2D cache " + itos(i) + " is out of date. Attempting to update..."); + _physical_bone_update_cache(i); + continue; + } + + PhysicalBone2D *physical_bone = Object::cast_to<PhysicalBone2D>(ObjectDB::get_instance(bone_data.physical_bone_node_cache)); + if (!physical_bone) { + ERR_PRINT_ONCE("PhysicalBone2D not found at index " + itos(i) + "!"); + return; + } + if (physical_bone->get_bone2d_index() < 0 || physical_bone->get_bone2d_index() > stack->skeleton->get_bone_count()) { + ERR_PRINT_ONCE("PhysicalBone2D at index " + itos(i) + " has invalid Bone2D!"); + return; + } + Bone2D *bone_2d = stack->skeleton->get_bone(physical_bone->get_bone2d_index()); + + if (physical_bone->get_simulate_physics() && !physical_bone->get_follow_bone_when_simulating()) { + bone_2d->set_global_transform(physical_bone->get_global_transform()); + stack->skeleton->set_bone_local_pose_override(physical_bone->get_bone2d_index(), bone_2d->get_transform(), stack->strength, true); + } + } +} + +void SkeletonModification2DPhysicalBones::_setup_modification(SkeletonModificationStack2D *p_stack) { + stack = p_stack; + + if (stack) { + is_setup = true; + + if (stack->skeleton) { + for (int i = 0; i < physical_bone_chain.size(); i++) { + _physical_bone_update_cache(i); + } + } + } +} + +void SkeletonModification2DPhysicalBones::_physical_bone_update_cache(int p_joint_idx) { + ERR_FAIL_INDEX_MSG(p_joint_idx, physical_bone_chain.size(), "Cannot update PhysicalBone2D cache: joint index out of range!"); + if (!is_setup || !stack) { + if (!stack) { + ERR_PRINT_ONCE("Cannot update PhysicalBone2D cache: modification is not properly setup!"); + } + return; + } + + physical_bone_chain.write[p_joint_idx].physical_bone_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(physical_bone_chain[p_joint_idx].physical_bone_node)) { + Node *node = stack->skeleton->get_node(physical_bone_chain[p_joint_idx].physical_bone_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update Physical Bone2D " + itos(p_joint_idx) + " cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update Physical Bone2D " + itos(p_joint_idx) + " cache: node is not in scene tree!"); + physical_bone_chain.write[p_joint_idx].physical_bone_node_cache = node->get_instance_id(); + } + } + } +} + +int SkeletonModification2DPhysicalBones::get_physical_bone_chain_length() { + return physical_bone_chain.size(); +} + +void SkeletonModification2DPhysicalBones::set_physical_bone_chain_length(int p_length) { + ERR_FAIL_COND(p_length < 0); + physical_bone_chain.resize(p_length); + notify_property_list_changed(); +} + +void SkeletonModification2DPhysicalBones::fetch_physical_bones() { + ERR_FAIL_COND_MSG(!stack, "No modification stack found! Cannot fetch physical bones!"); + ERR_FAIL_COND_MSG(!stack->skeleton, "No skeleton found! Cannot fetch physical bones!"); + + physical_bone_chain.clear(); + + List<Node *> node_queue = List<Node *>(); + node_queue.push_back(stack->skeleton); + + while (node_queue.size() > 0) { + Node *node_to_process = node_queue[0]; + node_queue.pop_front(); + + if (node_to_process != nullptr) { + PhysicalBone2D *potential_bone = Object::cast_to<PhysicalBone2D>(node_to_process); + if (potential_bone) { + PhysicalBone_Data2D new_data = PhysicalBone_Data2D(); + new_data.physical_bone_node = stack->skeleton->get_path_to(potential_bone); + new_data.physical_bone_node_cache = potential_bone->get_instance_id(); + physical_bone_chain.push_back(new_data); + } + for (int i = 0; i < node_to_process->get_child_count(); i++) { + node_queue.push_back(node_to_process->get_child(i)); + } + } + } +} + +void SkeletonModification2DPhysicalBones::start_simulation(const TypedArray<StringName> &p_bones) { + _simulation_state_dirty = true; + _simulation_state_dirty_names = p_bones; + _simulation_state_dirty_process = true; + + if (is_setup) { + _update_simulation_state(); + } +} + +void SkeletonModification2DPhysicalBones::stop_simulation(const TypedArray<StringName> &p_bones) { + _simulation_state_dirty = true; + _simulation_state_dirty_names = p_bones; + _simulation_state_dirty_process = false; + + if (is_setup) { + _update_simulation_state(); + } +} + +void SkeletonModification2DPhysicalBones::_update_simulation_state() { + if (!_simulation_state_dirty) { + return; + } + _simulation_state_dirty = false; + + if (_simulation_state_dirty_names.size() <= 0) { + for (int i = 0; i < physical_bone_chain.size(); i++) { + PhysicalBone2D *physical_bone = Object::cast_to<PhysicalBone2D>(stack->skeleton->get_node(physical_bone_chain[i].physical_bone_node)); + if (!physical_bone) { + continue; + } + + physical_bone->set_simulate_physics(_simulation_state_dirty_process); + } + } else { + for (int i = 0; i < physical_bone_chain.size(); i++) { + PhysicalBone2D *physical_bone = Object::cast_to<PhysicalBone2D>(ObjectDB::get_instance(physical_bone_chain[i].physical_bone_node_cache)); + if (!physical_bone) { + continue; + } + if (_simulation_state_dirty_names.has(physical_bone->get_name())) { + physical_bone->set_simulate_physics(_simulation_state_dirty_process); + } + } + } +} + +void SkeletonModification2DPhysicalBones::set_physical_bone_node(int p_joint_idx, const NodePath &p_nodepath) { + ERR_FAIL_INDEX_MSG(p_joint_idx, physical_bone_chain.size(), "Joint index out of range!"); + physical_bone_chain.write[p_joint_idx].physical_bone_node = p_nodepath; + _physical_bone_update_cache(p_joint_idx); +} + +NodePath SkeletonModification2DPhysicalBones::get_physical_bone_node(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, physical_bone_chain.size(), NodePath(), "Joint index out of range!"); + return physical_bone_chain[p_joint_idx].physical_bone_node; +} + +void SkeletonModification2DPhysicalBones::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_physical_bone_chain_length", "length"), &SkeletonModification2DPhysicalBones::set_physical_bone_chain_length); + ClassDB::bind_method(D_METHOD("get_physical_bone_chain_length"), &SkeletonModification2DPhysicalBones::get_physical_bone_chain_length); + + ClassDB::bind_method(D_METHOD("set_physical_bone_node", "joint_idx", "physicalbone2d_node"), &SkeletonModification2DPhysicalBones::set_physical_bone_node); + ClassDB::bind_method(D_METHOD("get_physical_bone_node", "joint_idx"), &SkeletonModification2DPhysicalBones::get_physical_bone_node); + + ClassDB::bind_method(D_METHOD("fetch_physical_bones"), &SkeletonModification2DPhysicalBones::fetch_physical_bones); + ClassDB::bind_method(D_METHOD("start_simulation", "bones"), &SkeletonModification2DPhysicalBones::start_simulation, DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("stop_simulation", "bones"), &SkeletonModification2DPhysicalBones::stop_simulation, DEFVAL(Array())); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "physical_bone_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_physical_bone_chain_length", "get_physical_bone_chain_length"); +} + +SkeletonModification2DPhysicalBones::SkeletonModification2DPhysicalBones() { + stack = nullptr; + is_setup = false; + physical_bone_chain = Vector<PhysicalBone_Data2D>(); + enabled = true; + editor_draw_gizmo = false; // Nothing to really show in a gizmo right now. +} + +SkeletonModification2DPhysicalBones::~SkeletonModification2DPhysicalBones() { +} diff --git a/scene/resources/skeleton_modification_2d_physicalbones.h b/scene/resources/skeleton_modification_2d_physicalbones.h new file mode 100644 index 0000000000..cdf6a5f570 --- /dev/null +++ b/scene/resources/skeleton_modification_2d_physicalbones.h @@ -0,0 +1,82 @@ +/*************************************************************************/ +/* skeleton_modification_2d_physicalbones.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SKELETONMODIFICATION2DPHYSICALBONES_H +#define SKELETONMODIFICATION2DPHYSICALBONES_H + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/skeleton_modification_2d.h" + +/////////////////////////////////////// +// SkeletonModification2DJIGGLE +/////////////////////////////////////// + +class SkeletonModification2DPhysicalBones : public SkeletonModification2D { + GDCLASS(SkeletonModification2DPhysicalBones, SkeletonModification2D); + +private: + struct PhysicalBone_Data2D { + NodePath physical_bone_node; + ObjectID physical_bone_node_cache; + }; + Vector<PhysicalBone_Data2D> physical_bone_chain; + + void _physical_bone_update_cache(int p_joint_idx); + + bool _simulation_state_dirty = false; + TypedArray<StringName> _simulation_state_dirty_names; + bool _simulation_state_dirty_process; + void _update_simulation_state(); + +protected: + static void _bind_methods(); + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + void _execute(float p_delta) override; + void _setup_modification(SkeletonModificationStack2D *p_stack) override; + + int get_physical_bone_chain_length(); + void set_physical_bone_chain_length(int p_new_length); + + void set_physical_bone_node(int p_joint_idx, const NodePath &p_path); + NodePath get_physical_bone_node(int p_joint_idx) const; + + void fetch_physical_bones(); + void start_simulation(const TypedArray<StringName> &p_bones); + void stop_simulation(const TypedArray<StringName> &p_bones); + + SkeletonModification2DPhysicalBones(); + ~SkeletonModification2DPhysicalBones(); +}; + +#endif // SKELETONMODIFICATION2DPHYSICALBONES_H diff --git a/scene/resources/skeleton_modification_2d_stackholder.cpp b/scene/resources/skeleton_modification_2d_stackholder.cpp new file mode 100644 index 0000000000..9436092cd9 --- /dev/null +++ b/scene/resources/skeleton_modification_2d_stackholder.cpp @@ -0,0 +1,131 @@ +/*************************************************************************/ +/* skeleton_modification_2d_stackholder.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "skeleton_modification_2d_stackholder.h" +#include "scene/2d/skeleton_2d.h" + +bool SkeletonModification2DStackHolder::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path == "held_modification_stack") { + set_held_modification_stack(p_value); + } + +#ifdef TOOLS_ENABLED + if (path == "editor/draw_gizmo") { + set_editor_draw_gizmo(p_value); + } +#endif // TOOLS_ENABLED + + return true; +} + +bool SkeletonModification2DStackHolder::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path == "held_modification_stack") { + r_ret = get_held_modification_stack(); + } + +#ifdef TOOLS_ENABLED + if (path == "editor/draw_gizmo") { + r_ret = get_editor_draw_gizmo(); + } +#endif // TOOLS_ENABLED + + return true; +} + +void SkeletonModification2DStackHolder::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::OBJECT, "held_modification_stack", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } +#endif // TOOLS_ENABLED +} + +void SkeletonModification2DStackHolder::_execute(float p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + + if (held_modification_stack.is_valid()) { + held_modification_stack->execute(p_delta, execution_mode); + } +} + +void SkeletonModification2DStackHolder::_setup_modification(SkeletonModificationStack2D *p_stack) { + stack = p_stack; + + if (stack != nullptr) { + is_setup = true; + + if (held_modification_stack.is_valid()) { + held_modification_stack->set_skeleton(stack->get_skeleton()); + held_modification_stack->setup(); + } + } +} + +void SkeletonModification2DStackHolder::_draw_editor_gizmo() { + if (stack) { + if (held_modification_stack.is_valid()) { + held_modification_stack->draw_editor_gizmos(); + } + } +} + +void SkeletonModification2DStackHolder::set_held_modification_stack(Ref<SkeletonModificationStack2D> p_held_stack) { + held_modification_stack = p_held_stack; + + if (is_setup && held_modification_stack.is_valid()) { + held_modification_stack->set_skeleton(stack->get_skeleton()); + held_modification_stack->setup(); + } +} + +Ref<SkeletonModificationStack2D> SkeletonModification2DStackHolder::get_held_modification_stack() const { + return held_modification_stack; +} + +void SkeletonModification2DStackHolder::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_held_modification_stack", "held_modification_stack"), &SkeletonModification2DStackHolder::set_held_modification_stack); + ClassDB::bind_method(D_METHOD("get_held_modification_stack"), &SkeletonModification2DStackHolder::get_held_modification_stack); +} + +SkeletonModification2DStackHolder::SkeletonModification2DStackHolder() { + stack = nullptr; + is_setup = false; + enabled = true; +} + +SkeletonModification2DStackHolder::~SkeletonModification2DStackHolder() { +} diff --git a/scene/resources/skeleton_modification_2d_stackholder.h b/scene/resources/skeleton_modification_2d_stackholder.h new file mode 100644 index 0000000000..9cc38e3942 --- /dev/null +++ b/scene/resources/skeleton_modification_2d_stackholder.h @@ -0,0 +1,64 @@ +/*************************************************************************/ +/* skeleton_modification_2d_stackholder.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SKELETONMODIFICATION2DSTACKHOLDER_H +#define SKELETONMODIFICATION2DSTACKHOLDER_H + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/skeleton_modification_2d.h" + +/////////////////////////////////////// +// SkeletonModification2DJIGGLE +/////////////////////////////////////// + +class SkeletonModification2DStackHolder : public SkeletonModification2D { + GDCLASS(SkeletonModification2DStackHolder, SkeletonModification2D); + +protected: + static void _bind_methods(); + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + Ref<SkeletonModificationStack2D> held_modification_stack; + + void _execute(float p_delta) override; + void _setup_modification(SkeletonModificationStack2D *p_stack) override; + void _draw_editor_gizmo() override; + + void set_held_modification_stack(Ref<SkeletonModificationStack2D> p_held_stack); + Ref<SkeletonModificationStack2D> get_held_modification_stack() const; + + SkeletonModification2DStackHolder(); + ~SkeletonModification2DStackHolder(); +}; + +#endif // SKELETONMODIFICATION2DSTACKHOLDER_H diff --git a/scene/resources/skeleton_modification_2d_twoboneik.cpp b/scene/resources/skeleton_modification_2d_twoboneik.cpp new file mode 100644 index 0000000000..0a91290015 --- /dev/null +++ b/scene/resources/skeleton_modification_2d_twoboneik.cpp @@ -0,0 +1,481 @@ +/*************************************************************************/ +/* skeleton_modification_2d_twoboneik.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "skeleton_modification_2d_twoboneik.h" +#include "scene/2d/skeleton_2d.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif // TOOLS_ENABLED + +bool SkeletonModification2DTwoBoneIK::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path == "joint_one_bone_idx") { + set_joint_one_bone_idx(p_value); + } else if (path == "joint_one_bone2d_node") { + set_joint_one_bone2d_node(p_value); + } else if (path == "joint_two_bone_idx") { + set_joint_two_bone_idx(p_value); + } else if (path == "joint_two_bone2d_node") { + set_joint_two_bone2d_node(p_value); + } + +#ifdef TOOLS_ENABLED + if (path.begins_with("editor/draw_gizmo")) { + set_editor_draw_gizmo(p_value); + } else if (path.begins_with("editor/draw_min_max")) { + set_editor_draw_min_max(p_value); + } +#endif // TOOLS_ENABLED + + return true; +} + +bool SkeletonModification2DTwoBoneIK::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path == "joint_one_bone_idx") { + r_ret = get_joint_one_bone_idx(); + } else if (path == "joint_one_bone2d_node") { + r_ret = get_joint_one_bone2d_node(); + } else if (path == "joint_two_bone_idx") { + r_ret = get_joint_two_bone_idx(); + } else if (path == "joint_two_bone2d_node") { + r_ret = get_joint_two_bone2d_node(); + } + +#ifdef TOOLS_ENABLED + if (path.begins_with("editor/draw_gizmo")) { + r_ret = get_editor_draw_gizmo(); + } else if (path.begins_with("editor/draw_min_max")) { + r_ret = get_editor_draw_min_max(); + } +#endif // TOOLS_ENABLED + + return true; +} + +void SkeletonModification2DTwoBoneIK::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::INT, "joint_one_bone_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, "joint_one_bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); + + p_list->push_back(PropertyInfo(Variant::INT, "joint_two_bone_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, "joint_two_bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_min_max", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } +#endif // TOOLS_ENABLED +} + +void SkeletonModification2DTwoBoneIK::_execute(float p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + + if (target_node_cache.is_null()) { + WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); + update_target_cache(); + return; + } + + if (joint_one_bone2d_node_cache.is_null() && !joint_one_bone2d_node.is_empty()) { + WARN_PRINT_ONCE("Joint one Bone2D node cache is out of date. Attempting to update..."); + update_joint_one_bone2d_cache(); + } + if (joint_two_bone2d_node_cache.is_null() && !joint_two_bone2d_node.is_empty()) { + WARN_PRINT_ONCE("Joint two Bone2D node cache is out of date. Attempting to update..."); + update_joint_two_bone2d_cache(); + } + + Node2D *target = Object::cast_to<Node2D>(ObjectDB::get_instance(target_node_cache)); + if (!target || !target->is_inside_tree()) { + ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); + return; + } + + Bone2D *joint_one_bone = stack->skeleton->get_bone(joint_one_bone_idx); + if (joint_one_bone == nullptr) { + ERR_PRINT_ONCE("Joint one bone_idx does not point to a valid bone! Cannot execute modification!"); + return; + } + + Bone2D *joint_two_bone = stack->skeleton->get_bone(joint_two_bone_idx); + if (joint_two_bone == nullptr) { + ERR_PRINT_ONCE("Joint two bone_idx does not point to a valid bone! Cannot execute modification!"); + return; + } + + // Adopted from the links below: + // http://theorangeduck.com/page/simple-two-joint + // https://www.alanzucconi.com/2018/05/02/ik-2d-2/ + // With modifications by TwistedTwigleg + Vector2 target_difference = target->get_global_transform().get_origin() - joint_one_bone->get_global_transform().get_origin(); + float joint_one_to_target = target_difference.length(); + float angle_atan = Math::atan2(target_difference.y, target_difference.x); + + float bone_one_length = joint_one_bone->get_length() * MIN(joint_one_bone->get_global_scale().x, joint_one_bone->get_global_scale().y); + float bone_two_length = joint_two_bone->get_length() * MIN(joint_two_bone->get_global_scale().x, joint_two_bone->get_global_scale().y); + bool override_angles_due_to_out_of_range = false; + + if (joint_one_to_target < target_minimum_distance) { + joint_one_to_target = target_minimum_distance; + } + if (joint_one_to_target > target_maximum_distance && target_maximum_distance > 0.0) { + joint_one_to_target = target_maximum_distance; + } + + if (bone_one_length + bone_two_length < joint_one_to_target) { + override_angles_due_to_out_of_range = true; + } + + if (!override_angles_due_to_out_of_range) { + float angle_0 = Math::acos(((joint_one_to_target * joint_one_to_target) + (bone_one_length * bone_one_length) - (bone_two_length * bone_two_length)) / (2.0 * joint_one_to_target * bone_one_length)); + float angle_1 = Math::acos(((bone_two_length * bone_two_length) + (bone_one_length * bone_one_length) - (joint_one_to_target * joint_one_to_target)) / (2.0 * bone_two_length * bone_one_length)); + + if (flip_bend_direction) { + angle_0 = -angle_0; + angle_1 = -angle_1; + } + + if (isnan(angle_0) || isnan(angle_1)) { + // We cannot solve for this angle! Do nothing to avoid setting the rotation (and scale) to NaN. + } else { + joint_one_bone->set_global_rotation(angle_atan - angle_0 - joint_one_bone->get_bone_angle()); + joint_two_bone->set_rotation(-Math_PI - angle_1 - joint_two_bone->get_bone_angle() + joint_one_bone->get_bone_angle()); + } + } else { + joint_one_bone->set_global_rotation(angle_atan - joint_one_bone->get_bone_angle()); + joint_two_bone->set_global_rotation(angle_atan - joint_two_bone->get_bone_angle()); + } + + stack->skeleton->set_bone_local_pose_override(joint_one_bone_idx, joint_one_bone->get_transform(), stack->strength, true); + stack->skeleton->set_bone_local_pose_override(joint_two_bone_idx, joint_two_bone->get_transform(), stack->strength, true); +} + +void SkeletonModification2DTwoBoneIK::_setup_modification(SkeletonModificationStack2D *p_stack) { + stack = p_stack; + + if (stack) { + is_setup = true; + update_target_cache(); + update_joint_one_bone2d_cache(); + update_joint_two_bone2d_cache(); + } +} + +void SkeletonModification2DTwoBoneIK::_draw_editor_gizmo() { + if (!enabled || !is_setup) { + return; + } + + Bone2D *operation_bone_one = stack->skeleton->get_bone(joint_one_bone_idx); + if (!operation_bone_one) { + return; + } + stack->skeleton->draw_set_transform( + stack->skeleton->get_global_transform().affine_inverse().xform(operation_bone_one->get_global_position()), + operation_bone_one->get_global_rotation() - stack->skeleton->get_global_rotation()); + + Color bone_ik_color = Color(1.0, 0.65, 0.0, 0.4); +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + bone_ik_color = EditorSettings::get_singleton()->get("editors/2d/bone_ik_color"); + } +#endif // TOOLS_ENABLED + + if (flip_bend_direction) { + float angle = -(Math_PI * 0.5) + operation_bone_one->get_bone_angle(); + stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(angle), sin(angle)) * (operation_bone_one->get_length() * 0.5), bone_ik_color, 2.0); + } else { + float angle = (Math_PI * 0.5) + operation_bone_one->get_bone_angle(); + stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(angle), sin(angle)) * (operation_bone_one->get_length() * 0.5), bone_ik_color, 2.0); + } + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + if (editor_draw_min_max) { + if (target_maximum_distance != 0.0 || target_minimum_distance != 0.0) { + Vector2 target_direction = Vector2(0, 1); + if (target_node_cache.is_valid()) { + stack->skeleton->draw_set_transform(Vector2(0, 0), 0.0); + Node2D *target = Object::cast_to<Node2D>(ObjectDB::get_instance(target_node_cache)); + target_direction = operation_bone_one->get_global_position().direction_to(target->get_global_position()); + } + + stack->skeleton->draw_circle(target_direction * target_minimum_distance, 8, bone_ik_color); + stack->skeleton->draw_circle(target_direction * target_maximum_distance, 8, bone_ik_color); + stack->skeleton->draw_line(target_direction * target_minimum_distance, target_direction * target_maximum_distance, bone_ik_color, 2.0); + } + } + } +#endif // TOOLS_ENABLED +} + +void SkeletonModification2DTwoBoneIK::update_target_cache() { + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + return; + } + + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: node is not in the scene tree!"); + target_node_cache = node->get_instance_id(); + } + } + } +} + +void SkeletonModification2DTwoBoneIK::update_joint_one_bone2d_cache() { + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update joint one Bone2D cache: modification is not properly setup!"); + return; + } + + joint_one_bone2d_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(joint_one_bone2d_node)) { + Node *node = stack->skeleton->get_node(joint_one_bone2d_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update update joint one Bone2D cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update update joint one Bone2D cache: node is not in the scene tree!"); + joint_one_bone2d_node_cache = node->get_instance_id(); + + Bone2D *bone = Object::cast_to<Bone2D>(node); + if (bone) { + joint_one_bone_idx = bone->get_index_in_skeleton(); + } else { + ERR_FAIL_MSG("update joint one Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); + } + } + } + } +} + +void SkeletonModification2DTwoBoneIK::update_joint_two_bone2d_cache() { + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update joint two Bone2D cache: modification is not properly setup!"); + return; + } + + joint_two_bone2d_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(joint_two_bone2d_node)) { + Node *node = stack->skeleton->get_node(joint_two_bone2d_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update update joint two Bone2D cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update update joint two Bone2D cache: node is not in scene tree!"); + joint_two_bone2d_node_cache = node->get_instance_id(); + + Bone2D *bone = Object::cast_to<Bone2D>(node); + if (bone) { + joint_two_bone_idx = bone->get_index_in_skeleton(); + } else { + ERR_FAIL_MSG("update joint two Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); + } + } + } + } +} + +void SkeletonModification2DTwoBoneIK::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_target_cache(); +} + +NodePath SkeletonModification2DTwoBoneIK::get_target_node() const { + return target_node; +} + +void SkeletonModification2DTwoBoneIK::set_joint_one_bone2d_node(const NodePath &p_target_node) { + joint_one_bone2d_node = p_target_node; + update_joint_one_bone2d_cache(); + notify_property_list_changed(); +} + +void SkeletonModification2DTwoBoneIK::set_target_minimum_distance(float p_distance) { + ERR_FAIL_COND_MSG(p_distance < 0, "Target minimum distance cannot be less than zero!"); + target_minimum_distance = p_distance; +} + +float SkeletonModification2DTwoBoneIK::get_target_minimum_distance() const { + return target_minimum_distance; +} + +void SkeletonModification2DTwoBoneIK::set_target_maximum_distance(float p_distance) { + ERR_FAIL_COND_MSG(p_distance < 0, "Target maximum distance cannot be less than zero!"); + target_maximum_distance = p_distance; +} + +float SkeletonModification2DTwoBoneIK::get_target_maximum_distance() const { + return target_maximum_distance; +} + +void SkeletonModification2DTwoBoneIK::set_flip_bend_direction(bool p_flip_direction) { + flip_bend_direction = p_flip_direction; + +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2DTwoBoneIK::get_flip_bend_direction() const { + return flip_bend_direction; +} + +NodePath SkeletonModification2DTwoBoneIK::get_joint_one_bone2d_node() const { + return joint_one_bone2d_node; +} + +void SkeletonModification2DTwoBoneIK::set_joint_two_bone2d_node(const NodePath &p_target_node) { + joint_two_bone2d_node = p_target_node; + update_joint_two_bone2d_cache(); + notify_property_list_changed(); +} + +NodePath SkeletonModification2DTwoBoneIK::get_joint_two_bone2d_node() const { + return joint_two_bone2d_node; +} + +void SkeletonModification2DTwoBoneIK::set_joint_one_bone_idx(int p_bone_idx) { + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + + if (is_setup) { + if (stack->skeleton) { + ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); + joint_one_bone_idx = p_bone_idx; + joint_one_bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); + joint_one_bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); + } else { + WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint one..."); + joint_one_bone_idx = p_bone_idx; + } + } else { + WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint one..."); + joint_one_bone_idx = p_bone_idx; + } + + notify_property_list_changed(); +} + +int SkeletonModification2DTwoBoneIK::get_joint_one_bone_idx() const { + return joint_one_bone_idx; +} + +void SkeletonModification2DTwoBoneIK::set_joint_two_bone_idx(int p_bone_idx) { + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + + if (is_setup) { + if (stack->skeleton) { + ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); + joint_two_bone_idx = p_bone_idx; + joint_two_bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); + joint_two_bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); + } else { + WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint two..."); + joint_two_bone_idx = p_bone_idx; + } + } else { + WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint two..."); + joint_two_bone_idx = p_bone_idx; + } + + notify_property_list_changed(); +} + +int SkeletonModification2DTwoBoneIK::get_joint_two_bone_idx() const { + return joint_two_bone_idx; +} + +#ifdef TOOLS_ENABLED +void SkeletonModification2DTwoBoneIK::set_editor_draw_min_max(bool p_draw) { + editor_draw_min_max = p_draw; +} + +bool SkeletonModification2DTwoBoneIK::get_editor_draw_min_max() const { + return editor_draw_min_max; +} +#endif // TOOLS_ENABLED + +void SkeletonModification2DTwoBoneIK::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DTwoBoneIK::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DTwoBoneIK::get_target_node); + + ClassDB::bind_method(D_METHOD("set_target_minimum_distance", "minimum_distance"), &SkeletonModification2DTwoBoneIK::set_target_minimum_distance); + ClassDB::bind_method(D_METHOD("get_target_minimum_distance"), &SkeletonModification2DTwoBoneIK::get_target_minimum_distance); + ClassDB::bind_method(D_METHOD("set_target_maximum_distance", "maximum_distance"), &SkeletonModification2DTwoBoneIK::set_target_maximum_distance); + ClassDB::bind_method(D_METHOD("get_target_maximum_distance"), &SkeletonModification2DTwoBoneIK::get_target_maximum_distance); + ClassDB::bind_method(D_METHOD("set_flip_bend_direction", "flip_direction"), &SkeletonModification2DTwoBoneIK::set_flip_bend_direction); + ClassDB::bind_method(D_METHOD("get_flip_bend_direction"), &SkeletonModification2DTwoBoneIK::get_flip_bend_direction); + + ClassDB::bind_method(D_METHOD("set_joint_one_bone2d_node", "bone2d_node"), &SkeletonModification2DTwoBoneIK::set_joint_one_bone2d_node); + ClassDB::bind_method(D_METHOD("get_joint_one_bone2d_node"), &SkeletonModification2DTwoBoneIK::get_joint_one_bone2d_node); + ClassDB::bind_method(D_METHOD("set_joint_one_bone_idx", "bone_idx"), &SkeletonModification2DTwoBoneIK::set_joint_one_bone_idx); + ClassDB::bind_method(D_METHOD("get_joint_one_bone_idx"), &SkeletonModification2DTwoBoneIK::get_joint_one_bone_idx); + + ClassDB::bind_method(D_METHOD("set_joint_two_bone2d_node", "bone2d_node"), &SkeletonModification2DTwoBoneIK::set_joint_two_bone2d_node); + ClassDB::bind_method(D_METHOD("get_joint_two_bone2d_node"), &SkeletonModification2DTwoBoneIK::get_joint_two_bone2d_node); + ClassDB::bind_method(D_METHOD("set_joint_two_bone_idx", "bone_idx"), &SkeletonModification2DTwoBoneIK::set_joint_two_bone_idx); + ClassDB::bind_method(D_METHOD("get_joint_two_bone_idx"), &SkeletonModification2DTwoBoneIK::get_joint_two_bone_idx); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_minimum_distance", PROPERTY_HINT_RANGE, "0, 100000000, 0.01"), "set_target_minimum_distance", "get_target_minimum_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_maximum_distance", PROPERTY_HINT_NONE, "0, 100000000, 0.01"), "set_target_maximum_distance", "get_target_maximum_distance"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_bend_direction", PROPERTY_HINT_NONE, ""), "set_flip_bend_direction", "get_flip_bend_direction"); + ADD_GROUP("", ""); +} + +SkeletonModification2DTwoBoneIK::SkeletonModification2DTwoBoneIK() { + stack = nullptr; + is_setup = false; + enabled = true; + editor_draw_gizmo = true; +} + +SkeletonModification2DTwoBoneIK::~SkeletonModification2DTwoBoneIK() { +} diff --git a/scene/resources/skeleton_modification_2d_twoboneik.h b/scene/resources/skeleton_modification_2d_twoboneik.h new file mode 100644 index 0000000000..c7e545a488 --- /dev/null +++ b/scene/resources/skeleton_modification_2d_twoboneik.h @@ -0,0 +1,107 @@ +/*************************************************************************/ +/* skeleton_modification_2d_twoboneik.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SKELETONMODIFICATION2DTWOBONEIK_H +#define SKELETONMODIFICATION2DTWOBONEIK_H + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/skeleton_modification_2d.h" + +/////////////////////////////////////// +// SkeletonModification2DJIGGLE +/////////////////////////////////////// + +class SkeletonModification2DTwoBoneIK : public SkeletonModification2D { + GDCLASS(SkeletonModification2DTwoBoneIK, SkeletonModification2D); + +private: + NodePath target_node; + ObjectID target_node_cache; + float target_minimum_distance = 0; + float target_maximum_distance = 0; + bool flip_bend_direction = false; + + NodePath joint_one_bone2d_node; + ObjectID joint_one_bone2d_node_cache; + int joint_one_bone_idx = -1; + + NodePath joint_two_bone2d_node; + ObjectID joint_two_bone2d_node_cache; + int joint_two_bone_idx = -1; + +#ifdef TOOLS_ENABLED + bool editor_draw_min_max = false; +#endif // TOOLS_ENABLED + + void update_target_cache(); + void update_joint_one_bone2d_cache(); + void update_joint_two_bone2d_cache(); + +protected: + static void _bind_methods(); + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + void _execute(float p_delta) override; + void _setup_modification(SkeletonModificationStack2D *p_stack) override; + void _draw_editor_gizmo() override; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + + void set_target_minimum_distance(float p_minimum_distance); + float get_target_minimum_distance() const; + void set_target_maximum_distance(float p_maximum_distance); + float get_target_maximum_distance() const; + void set_flip_bend_direction(bool p_flip_direction); + bool get_flip_bend_direction() const; + + void set_joint_one_bone2d_node(const NodePath &p_node); + NodePath get_joint_one_bone2d_node() const; + void set_joint_one_bone_idx(int p_bone_idx); + int get_joint_one_bone_idx() const; + + void set_joint_two_bone2d_node(const NodePath &p_node); + NodePath get_joint_two_bone2d_node() const; + void set_joint_two_bone_idx(int p_bone_idx); + int get_joint_two_bone_idx() const; + +#ifdef TOOLS_ENABLED + void set_editor_draw_min_max(bool p_draw); + bool get_editor_draw_min_max() const; +#endif // TOOLS_ENABLED + + SkeletonModification2DTwoBoneIK(); + ~SkeletonModification2DTwoBoneIK(); +}; + +#endif // SKELETONMODIFICATION2DTWOBONEIK_H diff --git a/scene/resources/skeleton_modification_stack_2d.cpp b/scene/resources/skeleton_modification_stack_2d.cpp new file mode 100644 index 0000000000..72c1c330ef --- /dev/null +++ b/scene/resources/skeleton_modification_stack_2d.cpp @@ -0,0 +1,269 @@ +/*************************************************************************/ +/* skeleton_modification_stack_2d.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "skeleton_modification_stack_2d.h" +#include "scene/2d/skeleton_2d.h" + +void SkeletonModificationStack2D::_get_property_list(List<PropertyInfo> *p_list) const { + for (int i = 0; i < modifications.size(); i++) { + p_list->push_back( + PropertyInfo(Variant::OBJECT, "modifications/" + itos(i), + PROPERTY_HINT_RESOURCE_TYPE, + "SkeletonModification2D", + PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); + } +} + +bool SkeletonModificationStack2D::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("modifications/")) { + int mod_idx = path.get_slicec('/', 1).to_int(); + set_modification(mod_idx, p_value); + return true; + } + return true; +} + +bool SkeletonModificationStack2D::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("modifications/")) { + int mod_idx = path.get_slicec('/', 1).to_int(); + r_ret = get_modification(mod_idx); + return true; + } + return true; +} + +void SkeletonModificationStack2D::setup() { + if (is_setup) { + return; + } + + if (skeleton != nullptr) { + is_setup = true; + for (int i = 0; i < modifications.size(); i++) { + if (!modifications[i].is_valid()) { + continue; + } + modifications.get(i)->_setup_modification(this); + } + +#ifdef TOOLS_ENABLED + set_editor_gizmos_dirty(true); +#endif // TOOLS_ENABLED + + } else { + WARN_PRINT("Cannot setup SkeletonModificationStack2D: no Skeleton2D set!"); + } +} + +void SkeletonModificationStack2D::execute(float p_delta, int p_execution_mode) { + ERR_FAIL_COND_MSG(!is_setup || skeleton == nullptr || is_queued_for_deletion(), + "Modification stack is not properly setup and therefore cannot execute!"); + + if (!skeleton->is_inside_tree()) { + ERR_PRINT_ONCE("Skeleton is not inside SceneTree! Cannot execute modification!"); + return; + } + + if (!enabled) { + return; + } + + for (int i = 0; i < modifications.size(); i++) { + if (!modifications[i].is_valid()) { + continue; + } + + if (modifications[i]->get_execution_mode() == p_execution_mode) { + modifications.get(i)->_execute(p_delta); + } + } +} + +void SkeletonModificationStack2D::draw_editor_gizmos() { + if (!is_setup) { + return; + } + + if (editor_gizmo_dirty) { + for (int i = 0; i < modifications.size(); i++) { + if (!modifications[i].is_valid()) { + continue; + } + + if (modifications[i]->editor_draw_gizmo) { + modifications.get(i)->_draw_editor_gizmo(); + } + } + skeleton->draw_set_transform(Vector2(0, 0)); + editor_gizmo_dirty = false; + } +} + +void SkeletonModificationStack2D::set_editor_gizmos_dirty(bool p_dirty) { + if (!is_setup) { + return; + } + + if (!editor_gizmo_dirty && p_dirty) { + editor_gizmo_dirty = p_dirty; + if (skeleton) { + skeleton->update(); + } + } else { + editor_gizmo_dirty = p_dirty; + } +} + +void SkeletonModificationStack2D::enable_all_modifications(bool p_enabled) { + for (int i = 0; i < modifications.size(); i++) { + if (!modifications[i].is_valid()) { + continue; + } + modifications.get(i)->set_enabled(p_enabled); + } +} + +Ref<SkeletonModification2D> SkeletonModificationStack2D::get_modification(int p_mod_idx) const { + ERR_FAIL_INDEX_V(p_mod_idx, modifications.size(), nullptr); + return modifications[p_mod_idx]; +} + +void SkeletonModificationStack2D::add_modification(Ref<SkeletonModification2D> p_mod) { + ERR_FAIL_COND(!p_mod.is_valid()); + + p_mod->_setup_modification(this); + modifications.push_back(p_mod); + +#ifdef TOOLS_ENABLED + set_editor_gizmos_dirty(true); +#endif // TOOLS_ENABLED +} + +void SkeletonModificationStack2D::delete_modification(int p_mod_idx) { + ERR_FAIL_INDEX(p_mod_idx, modifications.size()); + modifications.remove(p_mod_idx); + +#ifdef TOOLS_ENABLED + set_editor_gizmos_dirty(true); +#endif // TOOLS_ENABLED +} + +void SkeletonModificationStack2D::set_modification(int p_mod_idx, Ref<SkeletonModification2D> p_mod) { + ERR_FAIL_INDEX(p_mod_idx, modifications.size()); + + if (p_mod == nullptr) { + modifications.insert(p_mod_idx, nullptr); + } else { + p_mod->_setup_modification(this); + modifications.insert(p_mod_idx, p_mod); + } + +#ifdef TOOLS_ENABLED + set_editor_gizmos_dirty(true); +#endif // TOOLS_ENABLED +} + +void SkeletonModificationStack2D::set_modification_count(int p_count) { + modifications.resize(p_count); + notify_property_list_changed(); + +#ifdef TOOLS_ENABLED + set_editor_gizmos_dirty(true); +#endif // TOOLS_ENABLED +} + +int SkeletonModificationStack2D::get_modification_count() const { + return modifications.size(); +} + +void SkeletonModificationStack2D::set_skeleton(Skeleton2D *p_skeleton) { + skeleton = p_skeleton; +} + +Skeleton2D *SkeletonModificationStack2D::get_skeleton() const { + return skeleton; +} + +bool SkeletonModificationStack2D::get_is_setup() const { + return is_setup; +} + +void SkeletonModificationStack2D::set_enabled(bool p_enabled) { + enabled = p_enabled; +} + +bool SkeletonModificationStack2D::get_enabled() const { + return enabled; +} + +void SkeletonModificationStack2D::set_strength(float p_strength) { + ERR_FAIL_COND_MSG(p_strength < 0, "Strength cannot be less than zero!"); + ERR_FAIL_COND_MSG(p_strength > 1, "Strength cannot be more than one!"); + strength = p_strength; +} + +float SkeletonModificationStack2D::get_strength() const { + return strength; +} + +void SkeletonModificationStack2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("setup"), &SkeletonModificationStack2D::setup); + ClassDB::bind_method(D_METHOD("execute", "delta", "execution_mode"), &SkeletonModificationStack2D::execute); + + ClassDB::bind_method(D_METHOD("enable_all_modifications", "enabled"), &SkeletonModificationStack2D::enable_all_modifications); + ClassDB::bind_method(D_METHOD("get_modification", "mod_idx"), &SkeletonModificationStack2D::get_modification); + ClassDB::bind_method(D_METHOD("add_modification", "modification"), &SkeletonModificationStack2D::add_modification); + ClassDB::bind_method(D_METHOD("delete_modification", "mod_idx"), &SkeletonModificationStack2D::delete_modification); + ClassDB::bind_method(D_METHOD("set_modification", "mod_idx", "modification"), &SkeletonModificationStack2D::set_modification); + + ClassDB::bind_method(D_METHOD("set_modification_count"), &SkeletonModificationStack2D::set_modification_count); + ClassDB::bind_method(D_METHOD("get_modification_count"), &SkeletonModificationStack2D::get_modification_count); + + ClassDB::bind_method(D_METHOD("get_is_setup"), &SkeletonModificationStack2D::get_is_setup); + + ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModificationStack2D::set_enabled); + ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModificationStack2D::get_enabled); + + ClassDB::bind_method(D_METHOD("set_strength", "strength"), &SkeletonModificationStack2D::set_strength); + ClassDB::bind_method(D_METHOD("get_strength"), &SkeletonModificationStack2D::get_strength); + + ClassDB::bind_method(D_METHOD("get_skeleton"), &SkeletonModificationStack2D::get_skeleton); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "strength", PROPERTY_HINT_RANGE, "0, 1, 0.001"), "set_strength", "get_strength"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "modification_count", PROPERTY_HINT_RANGE, "0, 100, 1"), "set_modification_count", "get_modification_count"); +} + +SkeletonModificationStack2D::SkeletonModificationStack2D() { +} diff --git a/scene/resources/skeleton_modification_stack_2d.h b/scene/resources/skeleton_modification_stack_2d.h new file mode 100644 index 0000000000..58855701a1 --- /dev/null +++ b/scene/resources/skeleton_modification_stack_2d.h @@ -0,0 +1,99 @@ +/*************************************************************************/ +/* skeleton_modification_stack_2d.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SKELETONMODIFICATIONSTACK2D_H +#define SKELETONMODIFICATIONSTACK2D_H + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/skeleton_modification_2d.h" + +/////////////////////////////////////// +// SkeletonModificationStack2D +/////////////////////////////////////// + +class Skeleton2D; +class SkeletonModification2D; +class Bone2D; + +class SkeletonModificationStack2D : public Resource { + GDCLASS(SkeletonModificationStack2D, Resource); + friend class Skeleton2D; + friend class SkeletonModification2D; + +protected: + static void _bind_methods(); + void _get_property_list(List<PropertyInfo> *p_list) const; + bool _set(const StringName &p_path, const Variant &p_value); + bool _get(const StringName &p_path, Variant &r_ret) const; + +public: + Skeleton2D *skeleton = nullptr; + bool is_setup = false; + bool enabled = false; + float strength = 1.0; + + enum EXECUTION_MODE { + execution_mode_process, + execution_mode_physics_process + }; + + Vector<Ref<SkeletonModification2D>> modifications = Vector<Ref<SkeletonModification2D>>(); + + void setup(); + void execute(float p_delta, int p_execution_mode); + + bool editor_gizmo_dirty = false; + void draw_editor_gizmos(); + void set_editor_gizmos_dirty(bool p_dirty); + + void enable_all_modifications(bool p_enable); + Ref<SkeletonModification2D> get_modification(int p_mod_idx) const; + void add_modification(Ref<SkeletonModification2D> p_mod); + void delete_modification(int p_mod_idx); + void set_modification(int p_mod_idx, Ref<SkeletonModification2D> p_mod); + + void set_modification_count(int p_count); + int get_modification_count() const; + + void set_skeleton(Skeleton2D *p_skeleton); + Skeleton2D *get_skeleton() const; + + bool get_is_setup() const; + + void set_enabled(bool p_enabled); + bool get_enabled() const; + + void set_strength(float p_strength); + float get_strength() const; + + SkeletonModificationStack2D(); +}; + +#endif // SKELETONMODIFICATION2D_H diff --git a/scene/resources/skin.cpp b/scene/resources/skin.cpp index fee8fdbde2..710612ae05 100644 --- a/scene/resources/skin.cpp +++ b/scene/resources/skin.cpp @@ -38,14 +38,14 @@ void Skin::set_bind_count(int p_size) { emit_changed(); } -void Skin::add_bind(int p_bone, const Transform &p_pose) { +void Skin::add_bind(int p_bone, const Transform3D &p_pose) { uint32_t index = bind_count; set_bind_count(bind_count + 1); set_bind_bone(index, p_bone); set_bind_pose(index, p_pose); } -void Skin::add_named_bind(const String &p_name, const Transform &p_pose) { +void Skin::add_named_bind(const String &p_name, const Transform3D &p_pose) { uint32_t index = bind_count; set_bind_count(bind_count + 1); set_bind_name(index, p_name); @@ -68,7 +68,7 @@ void Skin::set_bind_bone(int p_index, int p_bone) { emit_changed(); } -void Skin::set_bind_pose(int p_index, const Transform &p_pose) { +void Skin::set_bind_pose(int p_index, const Transform3D &p_pose) { ERR_FAIL_INDEX(p_index, bind_count); binds_ptr[p_index].pose = p_pose; emit_changed(); @@ -134,7 +134,7 @@ void Skin::_get_property_list(List<PropertyInfo> *p_list) const { for (int i = 0; i < get_bind_count(); i++) { p_list->push_back(PropertyInfo(Variant::STRING_NAME, "bind/" + itos(i) + "/name")); p_list->push_back(PropertyInfo(Variant::INT, "bind/" + itos(i) + "/bone", PROPERTY_HINT_RANGE, "0,16384,1,or_greater", get_bind_name(i) != StringName() ? PROPERTY_USAGE_NOEDITOR : PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::TRANSFORM, "bind/" + itos(i) + "/pose")); + p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, "bind/" + itos(i) + "/pose")); } } diff --git a/scene/resources/skin.h b/scene/resources/skin.h index f5d64f96aa..6857bf743a 100644 --- a/scene/resources/skin.h +++ b/scene/resources/skin.h @@ -39,7 +39,7 @@ class Skin : public Resource { struct Bind { int bone = -1; StringName name; - Transform pose; + Transform3D pose; }; Vector<Bind> binds; @@ -59,11 +59,11 @@ public: void set_bind_count(int p_size); inline int get_bind_count() const { return bind_count; } - void add_bind(int p_bone, const Transform &p_pose); - void add_named_bind(const String &p_name, const Transform &p_pose); + void add_bind(int p_bone, const Transform3D &p_pose); + void add_named_bind(const String &p_name, const Transform3D &p_pose); void set_bind_bone(int p_index, int p_bone); - void set_bind_pose(int p_index, const Transform &p_pose); + void set_bind_pose(int p_index, const Transform3D &p_pose); void set_bind_name(int p_index, const StringName &p_name); inline int get_bind_bone(int p_index) const { @@ -80,9 +80,9 @@ public: return binds_ptr[p_index].name; } - inline Transform get_bind_pose(int p_index) const { + inline Transform3D get_bind_pose(int p_index) const { #ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(p_index, bind_count, Transform()); + ERR_FAIL_INDEX_V(p_index, bind_count, Transform3D()); #endif return binds_ptr[p_index].pose; } diff --git a/scene/resources/sky_material.cpp b/scene/resources/sky_material.cpp index b2efecb1cb..89b3336118 100644 --- a/scene/resources/sky_material.cpp +++ b/scene/resources/sky_material.cpp @@ -193,8 +193,7 @@ ProceduralSkyMaterial::ProceduralSkyMaterial() { code += "uniform float ground_energy = 1.0;\n\n"; code += "uniform float sun_angle_max = 1.74;\n"; code += "uniform float sun_curve : hint_range(0, 1) = 0.05;\n\n"; - code += "const float PI = 3.1415926535897932384626433833;\n\n"; - code += "void fragment() {\n"; + code += "void sky() {\n"; code += "\tfloat v_angle = acos(clamp(EYEDIR.y, -1.0, 1.0));\n"; code += "\tfloat c = (1.0 - v_angle / (PI * 0.5));\n"; code += "\tvec3 sky = mix(sky_horizon_color.rgb, sky_top_color.rgb, clamp(1.0 - pow(1.0 - c, 1.0 / sky_curve), 0.0, 1.0));\n"; @@ -271,7 +270,8 @@ ProceduralSkyMaterial::~ProceduralSkyMaterial() { void PanoramaSkyMaterial::set_panorama(const Ref<Texture2D> &p_panorama) { panorama = p_panorama; - RS::get_singleton()->material_set_param(_get_material(), "source_panorama", panorama); + RID tex_rid = p_panorama.is_valid() ? p_panorama->get_rid() : RID(); + RS::get_singleton()->material_set_param(_get_material(), "source_panorama", tex_rid); } Ref<Texture2D> PanoramaSkyMaterial::get_panorama() const { @@ -301,7 +301,7 @@ PanoramaSkyMaterial::PanoramaSkyMaterial() { String code = "shader_type sky;\n\n"; code += "uniform sampler2D source_panorama : filter_linear;\n"; - code += "void fragment() {\n"; + code += "void sky() {\n"; code += "\tCOLOR = texture(source_panorama, SKY_COORDS).rgb;\n"; code += "}"; @@ -412,7 +412,8 @@ float PhysicalSkyMaterial::get_dither_strength() const { void PhysicalSkyMaterial::set_night_sky(const Ref<Texture2D> &p_night_sky) { night_sky = p_night_sky; - RS::get_singleton()->material_set_param(_get_material(), "night_sky", night_sky); + RID tex_rid = p_night_sky.is_valid() ? p_night_sky->get_rid() : RID(); + RS::get_singleton()->material_set_param(_get_material(), "night_sky", tex_rid); } Ref<Texture2D> PhysicalSkyMaterial::get_night_sky() const { @@ -499,7 +500,6 @@ PhysicalSkyMaterial::PhysicalSkyMaterial() { code += "uniform sampler2D night_sky : hint_black;"; - code += "const float PI = 3.141592653589793238462643383279502884197169;\n"; code += "const vec3 UP = vec3( 0.0, 1.0, 0.0 );\n\n"; code += "// Sun constants\n"; @@ -521,7 +521,7 @@ PhysicalSkyMaterial::PhysicalSkyMaterial() { code += "\treturn fract(p.x * p.y * p.z * (p.x + p.y + p.z));\n"; code += "}\n\n"; - code += "void fragment() {\n"; + code += "void sky() {\n"; code += "\tif (LIGHT0_ENABLED) {\n"; code += "\t\tfloat zenith_angle = clamp( dot(UP, normalize(LIGHT0_DIRECTION)), -1.0, 1.0 );\n"; code += "\t\tfloat sun_energy = max(0.0, 1.0 - exp(-((PI * 0.5) - acos(zenith_angle)))) * SUN_ENERGY * LIGHT0_ENERGY;\n"; diff --git a/scene/resources/sprite_frames.cpp b/scene/resources/sprite_frames.cpp index 90c702081b..df80084c5c 100644 --- a/scene/resources/sprite_frames.cpp +++ b/scene/resources/sprite_frames.cpp @@ -228,7 +228,7 @@ void SpriteFrames::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_frames"), &SpriteFrames::_set_frames); ClassDB::bind_method(D_METHOD("_get_frames"), &SpriteFrames::_get_frames); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "frames", PROPERTY_HINT_NONE, "", 0), "_set_frames", "_get_frames"); //compatibility + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "frames", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "_set_frames", "_get_frames"); //compatibility ClassDB::bind_method(D_METHOD("_set_animations"), &SpriteFrames::_set_animations); ClassDB::bind_method(D_METHOD("_get_animations"), &SpriteFrames::_get_animations); diff --git a/scene/resources/style_box.cpp b/scene/resources/style_box.cpp index 2159f1bc97..87371224e0 100644 --- a/scene/resources/style_box.cpp +++ b/scene/resources/style_box.cpp @@ -121,7 +121,6 @@ void StyleBoxTexture::set_texture(Ref<Texture2D> p_texture) { } else { region_rect = Rect2(Point2(), texture->get_size()); } - emit_signal("texture_changed"); emit_changed(); } @@ -285,8 +284,6 @@ void StyleBoxTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("set_v_axis_stretch_mode", "mode"), &StyleBoxTexture::set_v_axis_stretch_mode); ClassDB::bind_method(D_METHOD("get_v_axis_stretch_mode"), &StyleBoxTexture::get_v_axis_stretch_mode); - ADD_SIGNAL(MethodInfo("texture_changed")); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region_rect"), "set_region_rect", "get_region_rect"); ADD_GROUP("Margin", "margin_"); diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index 3d3900ecc5..9f8c35b668 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -35,6 +35,7 @@ SurfaceTool::OptimizeVertexCacheFunc SurfaceTool::optimize_vertex_cache_func = nullptr; SurfaceTool::SimplifyFunc SurfaceTool::simplify_func = nullptr; +SurfaceTool::SimplifyWithAttribFunc SurfaceTool::simplify_with_attrib_func = nullptr; SurfaceTool::SimplifyScaleFunc SurfaceTool::simplify_scale_func = nullptr; SurfaceTool::SimplifySloppyFunc SurfaceTool::simplify_sloppy_func = nullptr; @@ -369,13 +370,13 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - w[idx + 0] = v.tangent.x; - w[idx + 1] = v.tangent.y; - w[idx + 2] = v.tangent.z; + w[idx * 4 + 0] = v.tangent.x; + w[idx * 4 + 1] = v.tangent.y; + w[idx * 4 + 2] = v.tangent.z; //float d = v.tangent.dot(v.binormal,v.normal); float d = v.binormal.dot(v.normal.cross(v.tangent)); - w[idx + 3] = d < 0 ? -1 : 1; + w[idx * 4 + 3] = d < 0 ? -1 : 1; } a[i] = array; @@ -536,12 +537,16 @@ Array SurfaceTool::commit_to_arrays() { int count = skin_weights == SKIN_8_WEIGHTS ? 8 : 4; Vector<int> array; array.resize(varr_len * count); + array.fill(0); int *w = array.ptrw(); for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - ERR_CONTINUE(v.bones.size() != count); + if (v.bones.size() > count) { + ERR_PRINT_ONCE(vformat("Invalid bones size %d vs count %d", v.bones.size(), count)); + continue; + } for (int j = 0; j < count; j++) { w[idx * count + j] = v.bones[j]; @@ -556,12 +561,16 @@ Array SurfaceTool::commit_to_arrays() { int count = skin_weights == SKIN_8_WEIGHTS ? 8 : 4; array.resize(varr_len * count); + array.fill(0.0f); float *w = array.ptrw(); for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - ERR_CONTINUE(v.weights.size() != count); + if (v.weights.size() > count) { + ERR_PRINT_ONCE(vformat("Invalid weight size %d vs count %d", v.weights.size(), count)); + continue; + } for (int j = 0; j < count; j++) { w[idx * count + j] = v.weights[j]; @@ -598,7 +607,7 @@ Ref<ArrayMesh> SurfaceTool::commit(const Ref<ArrayMesh> &p_existing, uint32_t p_ if (p_existing.is_valid()) { mesh = p_existing; } else { - mesh.instance(); + mesh.instantiate(); } int varr_len = vertex_array.size(); @@ -663,6 +672,8 @@ void SurfaceTool::deindex() { } void SurfaceTool::_create_list(const Ref<Mesh> &p_existing, int p_surface, LocalVector<Vertex> *r_vertex, LocalVector<int> *r_index, uint32_t &lformat) { + ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::_create_list() must be a valid object of type Mesh"); + Array arr = p_existing->surface_get_arrays(p_surface); ERR_FAIL_COND(arr.size() != RS::ARRAY_MAX); _create_list_from_arrays(arr, r_vertex, r_index, lformat); @@ -824,6 +835,8 @@ void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) { } void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) { + ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::create_from() must be a valid object of type Mesh"); + clear(); primitive = p_existing->surface_get_primitive_type(p_surface); _create_list(p_existing, p_surface, &vertex_array, &index_array, format); @@ -831,6 +844,8 @@ void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) { } void SurfaceTool::create_from_blend_shape(const Ref<Mesh> &p_existing, int p_surface, const String &p_blend_shape_name) { + ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::create_from_blend_shape() must be a valid object of type Mesh"); + clear(); primitive = p_existing->surface_get_primitive_type(p_surface); Array arr = p_existing->surface_get_blend_shape_arrays(p_surface); @@ -850,7 +865,9 @@ void SurfaceTool::create_from_blend_shape(const Ref<Mesh> &p_existing, int p_sur _create_list_from_arrays(arr[shape_idx], &vertex_array, &index_array, format); } -void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const Transform &p_xform) { +void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const Transform3D &p_xform) { + ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::append_from() must be a valid object of type Mesh"); + if (vertex_array.size() == 0) { primitive = p_existing->surface_get_primitive_type(p_surface); format = 0; @@ -1105,7 +1122,7 @@ void SurfaceTool::optimize_indices_for_cache() { ERR_FAIL_COND(index_array.size() == 0); LocalVector old_index_array = index_array; - zeromem(index_array.ptr(), index_array.size() * sizeof(int)); + memset(index_array.ptr(), 0, index_array.size() * sizeof(int)); optimize_vertex_cache_func((unsigned int *)index_array.ptr(), (unsigned int *)old_index_array.ptr(), old_index_array.size(), vertex_array.size()); } diff --git a/scene/resources/surface_tool.h b/scene/resources/surface_tool.h index 28addf2245..bde6702759 100644 --- a/scene/resources/surface_tool.h +++ b/scene/resources/surface_tool.h @@ -35,8 +35,8 @@ #include "scene/resources/mesh.h" #include "thirdparty/misc/mikktspace.h" -class SurfaceTool : public Reference { - GDCLASS(SurfaceTool, Reference); +class SurfaceTool : public RefCounted { + GDCLASS(SurfaceTool, RefCounted); public: struct Vertex { @@ -78,6 +78,8 @@ public: static OptimizeVertexCacheFunc optimize_vertex_cache_func; typedef size_t (*SimplifyFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, float *r_error); static SimplifyFunc simplify_func; + typedef size_t (*SimplifyWithAttribFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_data, size_t vertex_count, size_t vertex_stride, size_t target_index_count, float target_error, float *result_error, const float *attributes, const float *attribute_weights, size_t attribute_count); + static SimplifyWithAttribFunc simplify_with_attrib_func; typedef float (*SimplifyScaleFunc)(const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride); static SimplifyScaleFunc simplify_scale_func; typedef size_t (*SimplifySloppyFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, float *out_result_error); @@ -184,7 +186,7 @@ public: Array commit_to_arrays(); void create_from(const Ref<Mesh> &p_existing, int p_surface); void create_from_blend_shape(const Ref<Mesh> &p_existing, int p_surface, const String &p_blend_shape_name); - void append_from(const Ref<Mesh> &p_existing, int p_surface, const Transform &p_xform); + void append_from(const Ref<Mesh> &p_existing, int p_surface, const Transform3D &p_xform); Ref<ArrayMesh> commit(const Ref<ArrayMesh> &p_existing = Ref<ArrayMesh>(), uint32_t p_flags = 0); SurfaceTool(); diff --git a/scene/resources/text_file.cpp b/scene/resources/text_file.cpp index cf07003720..33bb0a83e9 100644 --- a/scene/resources/text_file.cpp +++ b/scene/resources/text_file.cpp @@ -30,7 +30,7 @@ #include "text_file.h" -#include "core/os/file_access.h" +#include "core/io/file_access.h" bool TextFile::has_text() const { return text != ""; @@ -55,10 +55,10 @@ Error TextFile::load_text(const String &p_path) { ERR_FAIL_COND_V_MSG(err, err, "Cannot open TextFile '" + p_path + "'."); - int len = f->get_len(); + uint64_t len = f->get_length(); sourcef.resize(len + 1); uint8_t *w = sourcef.ptrw(); - int r = f->get_buffer(w, len); + uint64_t r = f->get_buffer(w, len); f->close(); memdelete(f); ERR_FAIL_COND_V(r != len, ERR_CANT_OPEN); diff --git a/scene/resources/text_line.cpp b/scene/resources/text_line.cpp index 925867a1f2..f1eff6e84f 100644 --- a/scene/resources/text_line.cpp +++ b/scene/resources/text_line.cpp @@ -74,7 +74,7 @@ void TextLine::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flags", "flags"), &TextLine::set_flags); ClassDB::bind_method(D_METHOD("get_flags"), &TextLine::get_flags); - ADD_PROPERTY(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Kashida justification,Word justification,Trim edge spaces after justification,Justification only after last tab"), "set_flags", "get_flags"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Kashida Justify,Word Justify,Trim Edge Spaces After Justify,Justify Only After Last Tab"), "set_flags", "get_flags"); ClassDB::bind_method(D_METHOD("get_objects"), &TextLine::get_objects); ClassDB::bind_method(D_METHOD("get_object_rect", "key"), &TextLine::get_object_rect); diff --git a/scene/resources/text_line.h b/scene/resources/text_line.h index 74d4f2c32c..1b5c1a3123 100644 --- a/scene/resources/text_line.h +++ b/scene/resources/text_line.h @@ -36,8 +36,8 @@ /*************************************************************************/ -class TextLine : public Reference { - GDCLASS(TextLine, Reference); +class TextLine : public RefCounted { + GDCLASS(TextLine, RefCounted); RID rid; int spacing_top = 0; diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp index 444a4bb22a..958c94fe31 100644 --- a/scene/resources/text_paragraph.cpp +++ b/scene/resources/text_paragraph.cpp @@ -72,7 +72,7 @@ void TextParagraph::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flags", "flags"), &TextParagraph::set_flags); ClassDB::bind_method(D_METHOD("get_flags"), &TextParagraph::get_flags); - ADD_PROPERTY(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Kashida justification,Word justification,Trim edge spaces after justification,Justification only after last tab,Break mandatory,Break words,Break graphemes"), "set_flags", "get_flags"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Kashida Justify,Word Justify,Trim Edge Spaces After Justify,Justify Only After Last Tab,Break Mandatory,Break Words,Break Graphemes"), "set_flags", "get_flags"); ClassDB::bind_method(D_METHOD("set_width", "width"), &TextParagraph::set_width); ClassDB::bind_method(D_METHOD("get_width"), &TextParagraph::get_width); @@ -98,6 +98,9 @@ void TextParagraph::_bind_methods() { ClassDB::bind_method(D_METHOD("get_line_underline_position", "line"), &TextParagraph::get_line_underline_position); ClassDB::bind_method(D_METHOD("get_line_underline_thickness", "line"), &TextParagraph::get_line_underline_thickness); + ClassDB::bind_method(D_METHOD("get_spacing_top"), &TextParagraph::get_spacing_top); + ClassDB::bind_method(D_METHOD("get_spacing_bottom"), &TextParagraph::get_spacing_bottom); + ClassDB::bind_method(D_METHOD("get_dropcap_size"), &TextParagraph::get_dropcap_size); ClassDB::bind_method(D_METHOD("get_dropcap_lines"), &TextParagraph::get_dropcap_lines); @@ -266,6 +269,14 @@ bool TextParagraph::add_string(const String &p_text, const Ref<Font> &p_fonts, i return res; } +int TextParagraph::get_spacing_top() const { + return spacing_top; +} + +int TextParagraph::get_spacing_bottom() const { + return spacing_bottom; +} + void TextParagraph::_set_bidi_override(const Array &p_override) { Vector<Vector2i> overrides; for (int i = 0; i < p_override.size(); i++) { @@ -609,11 +620,13 @@ int TextParagraph::hit_test(const Point2 &p_coords) const { const_cast<TextParagraph *>(this)->_shape_lines(); Vector2 ofs; if (TS->shaped_text_get_orientation(rid) == TextServer::ORIENTATION_HORIZONTAL) { - if (ofs.y < 0) + if (ofs.y < 0) { return 0; + } } else { - if (ofs.x < 0) + if (ofs.x < 0) { return 0; + } } for (int i = 0; i < lines.size(); i++) { if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { diff --git a/scene/resources/text_paragraph.h b/scene/resources/text_paragraph.h index a16fa8c3c4..a34e745090 100644 --- a/scene/resources/text_paragraph.h +++ b/scene/resources/text_paragraph.h @@ -36,8 +36,8 @@ /*************************************************************************/ -class TextParagraph : public Reference { - GDCLASS(TextParagraph, Reference); +class TextParagraph : public RefCounted { + GDCLASS(TextParagraph, RefCounted); RID dropcap_rid; int dropcap_lines = 0; @@ -116,6 +116,9 @@ public: float get_line_underline_position(int p_line) const; float get_line_underline_thickness(int p_line) const; + int get_spacing_top() const; + int get_spacing_bottom() const; + Size2 get_dropcap_size() const; int get_dropcap_lines() const; diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index d2bb1338d8..26c0d432a9 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -71,7 +71,7 @@ void Texture2D::_bind_methods() { ClassDB::bind_method(D_METHOD("draw", "canvas_item", "position", "modulate", "transpose"), &Texture2D::draw, DEFVAL(Color(1, 1, 1)), DEFVAL(false)); ClassDB::bind_method(D_METHOD("draw_rect", "canvas_item", "rect", "tile", "modulate", "transpose"), &Texture2D::draw_rect, DEFVAL(Color(1, 1, 1)), DEFVAL(false)); ClassDB::bind_method(D_METHOD("draw_rect_region", "canvas_item", "rect", "src_rect", "modulate", "transpose", "clip_uv"), &Texture2D::draw_rect_region, DEFVAL(Color(1, 1, 1)), DEFVAL(false), DEFVAL(true)); - ClassDB::bind_method(D_METHOD("get_data"), &Texture2D::get_data); + ClassDB::bind_method(D_METHOD("get_image"), &Texture2D::get_image); ADD_GROUP("", ""); } @@ -88,7 +88,7 @@ void ImageTexture::reload_from_file() { } Ref<Image> img; - img.instance(); + img.instantiate(); if (ImageLoader::load_image(path, img) == OK) { create_from_image(img); @@ -116,7 +116,7 @@ bool ImageTexture::_set(const StringName &p_name, const Variant &p_value) { bool ImageTexture::_get(const StringName &p_name, Variant &r_ret) const { if (p_name == "image") { - r_ret = get_data(); + r_ret = get_image(); } else if (p_name == "size") { r_ret = Size2(w, h); } else { @@ -138,7 +138,7 @@ void ImageTexture::_reload_hook(const RID &p_hook) { } Ref<Image> img; - img.instance(); + img.instantiate(); Error err = ImageLoader::load_image(path, img); ERR_FAIL_COND_MSG(err != OK, "Cannot load image from path '" + path + "'."); @@ -173,7 +173,7 @@ Image::Format ImageTexture::get_format() const { return format; } -void ImageTexture::update(const Ref<Image> &p_image, bool p_immediate) { +void ImageTexture::update(const Ref<Image> &p_image) { ERR_FAIL_COND_MSG(p_image.is_null(), "Invalid image"); ERR_FAIL_COND_MSG(texture.is_null(), "Texture is not initialized."); ERR_FAIL_COND_MSG(p_image->get_width() != w || p_image->get_height() != h, @@ -183,11 +183,7 @@ void ImageTexture::update(const Ref<Image> &p_image, bool p_immediate) { ERR_FAIL_COND_MSG(mipmaps != p_image->has_mipmaps(), "The new image mipmaps configuration must match the texture's image mipmaps configuration"); - if (p_immediate) { - RenderingServer::get_singleton()->texture_2d_update_immediate(texture, p_image); - } else { - RenderingServer::get_singleton()->texture_2d_update(texture, p_image); - } + RenderingServer::get_singleton()->texture_2d_update(texture, p_image); notify_property_list_changed(); emit_changed(); @@ -200,7 +196,7 @@ void ImageTexture::_resource_path_changed() { String path = get_path(); } -Ref<Image> ImageTexture::get_data() const { +Ref<Image> ImageTexture::get_image() const { if (image_stored) { return RenderingServer::get_singleton()->texture_2d_get(texture); } else { @@ -251,14 +247,14 @@ void ImageTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, cons bool ImageTexture::is_pixel_opaque(int p_x, int p_y) const { if (!alpha_cache.is_valid()) { - Ref<Image> img = get_data(); + Ref<Image> img = get_image(); if (img.is_valid()) { if (img->is_compressed()) { //must decompress, if compressed Ref<Image> decom = img->duplicate(); decom->decompress(); img = decom; } - alpha_cache.instance(); + alpha_cache.instantiate(); alpha_cache->create_from_image_alpha(img); } } @@ -305,7 +301,7 @@ void ImageTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("create_from_image", "image"), &ImageTexture::create_from_image); ClassDB::bind_method(D_METHOD("get_format"), &ImageTexture::get_format); - ClassDB::bind_method(D_METHOD("update", "image", "immediate"), &ImageTexture::update, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("update", "image"), &ImageTexture::update); ClassDB::bind_method(D_METHOD("set_size_override", "size"), &ImageTexture::set_size_override); ClassDB::bind_method(D_METHOD("_reload_hook", "rid"), &ImageTexture::_reload_hook); } @@ -327,7 +323,7 @@ Ref<Image> StreamTexture2D::load_image_from_file(FileAccess *f, int p_size_limit uint32_t mipmaps = f->get_32(); Image::Format format = Image::Format(f->get_32()); - if (data_format == DATA_FORMAT_LOSSLESS || data_format == DATA_FORMAT_LOSSY || data_format == DATA_FORMAT_BASIS_UNIVERSAL) { + if (data_format == DATA_FORMAT_PNG || data_format == DATA_FORMAT_WEBP || data_format == DATA_FORMAT_BASIS_UNIVERSAL) { //look for a PNG or WEBP file inside int sw = w; @@ -335,7 +331,7 @@ Ref<Image> StreamTexture2D::load_image_from_file(FileAccess *f, int p_size_limit //mipmaps need to be read independently, they will be later combined Vector<Ref<Image>> mipmap_images; - int total_size = 0; + uint64_t total_size = 0; bool first = true; @@ -360,10 +356,10 @@ Ref<Image> StreamTexture2D::load_image_from_file(FileAccess *f, int p_size_limit Ref<Image> img; if (data_format == DATA_FORMAT_BASIS_UNIVERSAL) { img = Image::basis_universal_unpacker(pv); - } else if (data_format == DATA_FORMAT_LOSSLESS) { - img = Image::lossless_unpacker(pv); + } else if (data_format == DATA_FORMAT_PNG) { + img = Image::png_unpacker(pv); } else { - img = Image::lossy_unpacker(pv); + img = Image::webp_unpacker(pv); } if (img.is_null() || img->is_empty()) { @@ -390,7 +386,7 @@ Ref<Image> StreamTexture2D::load_image_from_file(FileAccess *f, int p_size_limit //print_line("mipmap read total: " + itos(mipmap_images.size())); Ref<Image> image; - image.instance(); + image.instantiate(); if (mipmap_images.size() == 1) { //only one image (which will most likely be the case anyway for this format) @@ -410,7 +406,7 @@ Ref<Image> StreamTexture2D::load_image_from_file(FileAccess *f, int p_size_limit Vector<uint8_t> id = mipmap_images[i]->get_data(); int len = id.size(); const uint8_t *r = id.ptr(); - copymem(&wr[ofs], r, len); + memcpy(&wr[ofs], r, len); ofs += len; } } @@ -442,7 +438,7 @@ Ref<Image> StreamTexture2D::load_image_from_file(FileAccess *f, int p_size_limit } Ref<Image> image; - image.instance(); + image.instantiate(); image->create(tw, th, mipmaps - i ? true : false, format, data); @@ -490,7 +486,7 @@ Image::Format StreamTexture2D::get_format() const { return format; } -Error StreamTexture2D::_load_data(const String &p_path, int &tw, int &th, int &tw_custom, int &th_custom, Ref<Image> &image, bool &r_request_3d, bool &r_request_normal, bool &r_request_roughness, int &mipmap_limit, int p_size_limit) { +Error StreamTexture2D::_load_data(const String &p_path, int &r_width, int &r_height, Ref<Image> &image, bool &r_request_3d, bool &r_request_normal, bool &r_request_roughness, int &mipmap_limit, int p_size_limit) { alpha_cache.unref(); ERR_FAIL_COND_V(image.is_null(), ERR_INVALID_PARAMETER); @@ -511,8 +507,8 @@ Error StreamTexture2D::_load_data(const String &p_path, int &tw, int &th, int &t memdelete(f); ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Stream texture file is too new."); } - tw_custom = f->get_32(); - th_custom = f->get_32(); + r_width = f->get_32(); + r_height = f->get_32(); uint32_t df = f->get_32(); //data format //skip reserved @@ -551,16 +547,16 @@ Error StreamTexture2D::_load_data(const String &p_path, int &tw, int &th, int &t } Error StreamTexture2D::load(const String &p_path) { - int lw, lh, lwc, lhc; + int lw, lh; Ref<Image> image; - image.instance(); + image.instantiate(); bool request_3d; bool request_normal; bool request_roughness; int mipmap_limit; - Error err = _load_data(p_path, lw, lh, lwc, lhc, image, request_3d, request_normal, request_roughness, mipmap_limit); + Error err = _load_data(p_path, lw, lh, image, request_3d, request_normal, request_roughness, mipmap_limit); if (err) { return err; } @@ -571,12 +567,12 @@ Error StreamTexture2D::load(const String &p_path) { } else { texture = RS::get_singleton()->texture_2d_create(image); } - if (lwc || lhc) { - RS::get_singleton()->texture_set_size_override(texture, lwc, lhc); + if (lw || lh) { + RS::get_singleton()->texture_set_size_override(texture, lw, lh); } - w = lwc ? lwc : lw; - h = lhc ? lhc : lh; + w = lw; + h = lh; path_to_file = p_path; format = image->get_format(); @@ -661,7 +657,7 @@ bool StreamTexture2D::has_alpha() const { return false; } -Ref<Image> StreamTexture2D::get_data() const { +Ref<Image> StreamTexture2D::get_image() const { if (texture.is_valid()) { return RS::get_singleton()->texture_2d_get(texture); } else { @@ -671,7 +667,7 @@ Ref<Image> StreamTexture2D::get_data() const { bool StreamTexture2D::is_pixel_opaque(int p_x, int p_y) const { if (!alpha_cache.is_valid()) { - Ref<Image> img = get_data(); + Ref<Image> img = get_image(); if (img.is_valid()) { if (img->is_compressed()) { //must decompress, if compressed Ref<Image> decom = img->duplicate(); @@ -679,7 +675,7 @@ bool StreamTexture2D::is_pixel_opaque(int p_x, int p_y) const { img = decom; } - alpha_cache.instance(); + alpha_cache.instantiate(); alpha_cache->create_from_image_alpha(img); } } @@ -738,7 +734,7 @@ StreamTexture2D::~StreamTexture2D() { RES ResourceFormatLoaderStreamTexture2D::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { Ref<StreamTexture2D> st; - st.instance(); + st.instantiate(); Error err = st->load(p_path); if (r_error) { *r_error = err; @@ -1036,7 +1032,7 @@ StreamTexture3D::~StreamTexture3D() { RES ResourceFormatLoaderStreamTexture3D::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { Ref<StreamTexture3D> st; - st.instance(); + st.instantiate(); Error err = st->load(p_path); if (r_error) { *r_error = err; @@ -1405,281 +1401,341 @@ MeshTexture::MeshTexture() { ////////////////////////////////////////// -int LargeTexture::get_width() const { - return size.width; -} - -int LargeTexture::get_height() const { - return size.height; -} +void CurveTexture::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_width", "width"), &CurveTexture::set_width); -RID LargeTexture::get_rid() const { - return RID(); -} + ClassDB::bind_method(D_METHOD("set_curve", "curve"), &CurveTexture::set_curve); + ClassDB::bind_method(D_METHOD("get_curve"), &CurveTexture::get_curve); -bool LargeTexture::has_alpha() const { - for (int i = 0; i < pieces.size(); i++) { - if (pieces[i].texture->has_alpha()) { - return true; - } - } + ClassDB::bind_method(D_METHOD("set_texture_mode", "texture_mode"), &CurveTexture::set_texture_mode); + ClassDB::bind_method(D_METHOD("get_texture_mode"), &CurveTexture::get_texture_mode); - return false; -} + ClassDB::bind_method(D_METHOD("_update"), &CurveTexture::_update); -int LargeTexture::add_piece(const Point2 &p_offset, const Ref<Texture2D> &p_texture) { - ERR_FAIL_COND_V(p_texture.is_null(), -1); - Piece p; - p.offset = p_offset; - p.texture = p_texture; - pieces.push_back(p); + ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,4096"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_mode", PROPERTY_HINT_ENUM, "RGB,Red"), "set_texture_mode", "get_texture_mode"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve"); - return pieces.size() - 1; + BIND_ENUM_CONSTANT(TEXTURE_MODE_RGB); + BIND_ENUM_CONSTANT(TEXTURE_MODE_RED); } -void LargeTexture::set_piece_offset(int p_idx, const Point2 &p_offset) { - ERR_FAIL_INDEX(p_idx, pieces.size()); - pieces.write[p_idx].offset = p_offset; -}; +void CurveTexture::set_width(int p_width) { + ERR_FAIL_COND(p_width < 32 || p_width > 4096); -void LargeTexture::set_piece_texture(int p_idx, const Ref<Texture2D> &p_texture) { - ERR_FAIL_COND(p_texture == this); - ERR_FAIL_COND(p_texture.is_null()); - ERR_FAIL_INDEX(p_idx, pieces.size()); - pieces.write[p_idx].texture = p_texture; -}; + if (_width == p_width) { + return; + } -void LargeTexture::set_size(const Size2 &p_size) { - size = p_size; + _width = p_width; + _update(); } -void LargeTexture::clear() { - pieces.clear(); - size = Size2i(); +int CurveTexture::get_width() const { + return _width; } -Array LargeTexture::_get_data() const { - Array arr; - for (int i = 0; i < pieces.size(); i++) { - arr.push_back(pieces[i].offset); - arr.push_back(pieces[i].texture); +void CurveTexture::ensure_default_setup(float p_min, float p_max) { + if (_curve.is_null()) { + Ref<Curve> curve = Ref<Curve>(memnew(Curve)); + curve->add_point(Vector2(0, 1)); + curve->add_point(Vector2(1, 1)); + curve->set_min_value(p_min); + curve->set_max_value(p_max); + set_curve(curve); + // Min and max is 0..1 by default } - arr.push_back(Size2(size)); - return arr; } -void LargeTexture::_set_data(const Array &p_array) { - ERR_FAIL_COND(p_array.size() < 1); - ERR_FAIL_COND(!(p_array.size() & 1)); - clear(); - for (int i = 0; i < p_array.size() - 1; i += 2) { - add_piece(p_array[i], p_array[i + 1]); +void CurveTexture::set_curve(Ref<Curve> p_curve) { + if (_curve != p_curve) { + if (_curve.is_valid()) { + _curve->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveTexture::_update)); + } + _curve = p_curve; + if (_curve.is_valid()) { + _curve->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveTexture::_update)); + } + _update(); } - size = Size2(p_array[p_array.size() - 1]); } -int LargeTexture::get_piece_count() const { - return pieces.size(); -} +void CurveTexture::_update() { + Vector<uint8_t> data; + data.resize(_width * sizeof(float) * (texture_mode == TEXTURE_MODE_RGB ? 3 : 1)); -Vector2 LargeTexture::get_piece_offset(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, pieces.size(), Vector2()); - return pieces[p_idx].offset; -} + // The array is locked in that scope + { + uint8_t *wd8 = data.ptrw(); + float *wd = (float *)wd8; -Ref<Texture2D> LargeTexture::get_piece_texture(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, pieces.size(), Ref<Texture2D>()); - return pieces[p_idx].texture; -} + if (_curve.is_valid()) { + Curve &curve = **_curve; + for (int i = 0; i < _width; ++i) { + float t = i / static_cast<float>(_width); + if (texture_mode == TEXTURE_MODE_RGB) { + wd[i * 3 + 0] = curve.interpolate_baked(t); + wd[i * 3 + 1] = wd[i * 3 + 0]; + wd[i * 3 + 2] = wd[i * 3 + 0]; + } else { + wd[i] = curve.interpolate_baked(t); + } + } -Ref<Image> LargeTexture::to_image() const { - Ref<Image> img = memnew(Image(this->get_width(), this->get_height(), false, Image::FORMAT_RGBA8)); - for (int i = 0; i < pieces.size(); i++) { - Ref<Image> src_img = pieces[i].texture->get_data(); - img->blit_rect(src_img, Rect2(0, 0, src_img->get_width(), src_img->get_height()), pieces[i].offset); + } else { + for (int i = 0; i < _width; ++i) { + if (texture_mode == TEXTURE_MODE_RGB) { + wd[i * 3 + 0] = 0; + wd[i * 3 + 1] = 0; + wd[i * 3 + 2] = 0; + } else { + wd[i] = 0; + } + } + } } - return img; -} - -void LargeTexture::_bind_methods() { - ClassDB::bind_method(D_METHOD("add_piece", "ofs", "texture"), &LargeTexture::add_piece); - ClassDB::bind_method(D_METHOD("set_piece_offset", "idx", "ofs"), &LargeTexture::set_piece_offset); - ClassDB::bind_method(D_METHOD("set_piece_texture", "idx", "texture"), &LargeTexture::set_piece_texture); - ClassDB::bind_method(D_METHOD("set_size", "size"), &LargeTexture::set_size); - ClassDB::bind_method(D_METHOD("clear"), &LargeTexture::clear); - - ClassDB::bind_method(D_METHOD("get_piece_count"), &LargeTexture::get_piece_count); - ClassDB::bind_method(D_METHOD("get_piece_offset", "idx"), &LargeTexture::get_piece_offset); - ClassDB::bind_method(D_METHOD("get_piece_texture", "idx"), &LargeTexture::get_piece_texture); + Ref<Image> image = memnew(Image(_width, 1, false, texture_mode == TEXTURE_MODE_RGB ? Image::FORMAT_RGBF : Image::FORMAT_RF, data)); - ClassDB::bind_method(D_METHOD("_set_data", "data"), &LargeTexture::_set_data); - ClassDB::bind_method(D_METHOD("_get_data"), &LargeTexture::_get_data); + if (_texture.is_valid()) { + if (_current_texture_mode != texture_mode || _current_width != _width) { + RID new_texture = RS::get_singleton()->texture_2d_create(image); + RS::get_singleton()->texture_replace(_texture, new_texture); + } else { + RS::get_singleton()->texture_2d_update(_texture, image); + } + } else { + _texture = RS::get_singleton()->texture_2d_create(image); + } + _current_texture_mode = texture_mode; + _current_width = _width; - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data"); + emit_changed(); } -void LargeTexture::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose) const { - for (int i = 0; i < pieces.size(); i++) { - // TODO - pieces[i].texture->draw(p_canvas_item, pieces[i].offset + p_pos, p_modulate, p_transpose); - } +Ref<Curve> CurveTexture::get_curve() const { + return _curve; } -void LargeTexture::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose) const { - //tiling not supported for this - if (size.x == 0 || size.y == 0) { +void CurveTexture::set_texture_mode(TextureMode p_mode) { + if (texture_mode == p_mode) { return; } - - Size2 scale = p_rect.size / size; - - for (int i = 0; i < pieces.size(); i++) { - // TODO - pieces[i].texture->draw_rect(p_canvas_item, Rect2(pieces[i].offset * scale + p_rect.position, pieces[i].texture->get_size() * scale), false, p_modulate, p_transpose); - } + texture_mode = p_mode; + _update(); +} +CurveTexture::TextureMode CurveTexture::get_texture_mode() const { + return texture_mode; } -void LargeTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) const { - //tiling not supported for this - if (p_src_rect.size.x == 0 || p_src_rect.size.y == 0) { - return; - } - - Size2 scale = p_rect.size / p_src_rect.size; - - for (int i = 0; i < pieces.size(); i++) { - // TODO - Rect2 rect(pieces[i].offset, pieces[i].texture->get_size()); - if (!p_src_rect.intersects(rect)) { - continue; - } - Rect2 local = p_src_rect.intersection(rect); - Rect2 target = local; - target.size *= scale; - target.position = p_rect.position + (p_src_rect.position + rect.position) * scale; - local.position -= rect.position; - pieces[i].texture->draw_rect_region(p_canvas_item, target, local, p_modulate, p_transpose, false); +RID CurveTexture::get_rid() const { + if (!_texture.is_valid()) { + _texture = RS::get_singleton()->texture_2d_placeholder_create(); } + return _texture; } -bool LargeTexture::is_pixel_opaque(int p_x, int p_y) const { - for (int i = 0; i < pieces.size(); i++) { - // TODO - if (!pieces[i].texture.is_valid()) { - continue; - } +CurveTexture::CurveTexture() {} - Rect2 rect(pieces[i].offset, pieces[i].texture->get_size()); - if (rect.has_point(Point2(p_x, p_y))) { - return pieces[i].texture->is_pixel_opaque(p_x - rect.position.x, p_y - rect.position.y); - } +CurveTexture::~CurveTexture() { + if (_texture.is_valid()) { + RS::get_singleton()->free(_texture); } - - return true; } -LargeTexture::LargeTexture() { -} +////////////////// -/////////////////// +void CurveXYZTexture::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_width", "width"), &CurveXYZTexture::set_width); -void CurveTexture::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_width", "width"), &CurveTexture::set_width); + ClassDB::bind_method(D_METHOD("set_curve_x", "curve"), &CurveXYZTexture::set_curve_x); + ClassDB::bind_method(D_METHOD("get_curve_x"), &CurveXYZTexture::get_curve_x); - ClassDB::bind_method(D_METHOD("set_curve", "curve"), &CurveTexture::set_curve); - ClassDB::bind_method(D_METHOD("get_curve"), &CurveTexture::get_curve); + ClassDB::bind_method(D_METHOD("set_curve_y", "curve"), &CurveXYZTexture::set_curve_y); + ClassDB::bind_method(D_METHOD("get_curve_y"), &CurveXYZTexture::get_curve_y); - ClassDB::bind_method(D_METHOD("_update"), &CurveTexture::_update); + ClassDB::bind_method(D_METHOD("set_curve_z", "curve"), &CurveXYZTexture::set_curve_z); + ClassDB::bind_method(D_METHOD("get_curve_z"), &CurveXYZTexture::get_curve_z); - ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "32,4096"), "set_width", "get_width"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve"); + ClassDB::bind_method(D_METHOD("_update"), &CurveXYZTexture::_update); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,4096"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve_x", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve_x", "get_curve_x"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve_y", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve_y", "get_curve_y"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve_z", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve_z", "get_curve_z"); } -void CurveTexture::set_width(int p_width) { +void CurveXYZTexture::set_width(int p_width) { ERR_FAIL_COND(p_width < 32 || p_width > 4096); + + if (_width == p_width) { + return; + } + _width = p_width; _update(); } -int CurveTexture::get_width() const { +int CurveXYZTexture::get_width() const { return _width; } -void CurveTexture::ensure_default_setup(float p_min, float p_max) { - if (_curve.is_null()) { +void CurveXYZTexture::ensure_default_setup(float p_min, float p_max) { + if (_curve_x.is_null()) { Ref<Curve> curve = Ref<Curve>(memnew(Curve)); curve->add_point(Vector2(0, 1)); curve->add_point(Vector2(1, 1)); curve->set_min_value(p_min); curve->set_max_value(p_max); - set_curve(curve); - // Min and max is 0..1 by default + set_curve_x(curve); + } + + if (_curve_y.is_null()) { + Ref<Curve> curve = Ref<Curve>(memnew(Curve)); + curve->add_point(Vector2(0, 1)); + curve->add_point(Vector2(1, 1)); + curve->set_min_value(p_min); + curve->set_max_value(p_max); + set_curve_y(curve); + } + + if (_curve_z.is_null()) { + Ref<Curve> curve = Ref<Curve>(memnew(Curve)); + curve->add_point(Vector2(0, 1)); + curve->add_point(Vector2(1, 1)); + curve->set_min_value(p_min); + curve->set_max_value(p_max); + set_curve_z(curve); } } -void CurveTexture::set_curve(Ref<Curve> p_curve) { - if (_curve != p_curve) { - if (_curve.is_valid()) { - _curve->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveTexture::_update)); +void CurveXYZTexture::set_curve_x(Ref<Curve> p_curve) { + if (_curve_x != p_curve) { + if (_curve_x.is_valid()) { + _curve_x->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveXYZTexture::_update)); } - _curve = p_curve; - if (_curve.is_valid()) { - _curve->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveTexture::_update)); + _curve_x = p_curve; + if (_curve_x.is_valid()) { + _curve_x->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveXYZTexture::_update), varray(), CONNECT_REFERENCE_COUNTED); } _update(); } } -void CurveTexture::_update() { +void CurveXYZTexture::set_curve_y(Ref<Curve> p_curve) { + if (_curve_y != p_curve) { + if (_curve_y.is_valid()) { + _curve_y->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveXYZTexture::_update)); + } + _curve_y = p_curve; + if (_curve_y.is_valid()) { + _curve_y->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveXYZTexture::_update), varray(), CONNECT_REFERENCE_COUNTED); + } + _update(); + } +} + +void CurveXYZTexture::set_curve_z(Ref<Curve> p_curve) { + if (_curve_z != p_curve) { + if (_curve_z.is_valid()) { + _curve_z->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveXYZTexture::_update)); + } + _curve_z = p_curve; + if (_curve_z.is_valid()) { + _curve_z->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveXYZTexture::_update), varray(), CONNECT_REFERENCE_COUNTED); + } + _update(); + } +} + +void CurveXYZTexture::_update() { Vector<uint8_t> data; - data.resize(_width * sizeof(float)); + data.resize(_width * sizeof(float) * 3); // The array is locked in that scope { uint8_t *wd8 = data.ptrw(); float *wd = (float *)wd8; - if (_curve.is_valid()) { - Curve &curve = **_curve; + if (_curve_x.is_valid()) { + Curve &curve_x = **_curve_x; for (int i = 0; i < _width; ++i) { float t = i / static_cast<float>(_width); - wd[i] = curve.interpolate_baked(t); + wd[i * 3 + 0] = curve_x.interpolate_baked(t); } } else { for (int i = 0; i < _width; ++i) { - wd[i] = 0; + wd[i * 3 + 0] = 0; + } + } + + if (_curve_y.is_valid()) { + Curve &curve_y = **_curve_y; + for (int i = 0; i < _width; ++i) { + float t = i / static_cast<float>(_width); + wd[i * 3 + 1] = curve_y.interpolate_baked(t); + } + + } else { + for (int i = 0; i < _width; ++i) { + wd[i * 3 + 1] = 0; + } + } + + if (_curve_z.is_valid()) { + Curve &curve_z = **_curve_z; + for (int i = 0; i < _width; ++i) { + float t = i / static_cast<float>(_width); + wd[i * 3 + 2] = curve_z.interpolate_baked(t); + } + + } else { + for (int i = 0; i < _width; ++i) { + wd[i * 3 + 2] = 0; } } } - Ref<Image> image = memnew(Image(_width, 1, false, Image::FORMAT_RF, data)); + Ref<Image> image = memnew(Image(_width, 1, false, Image::FORMAT_RGBF, data)); if (_texture.is_valid()) { - RID new_texture = RS::get_singleton()->texture_2d_create(image); - RS::get_singleton()->texture_replace(_texture, new_texture); + if (_current_width != _width) { + RID new_texture = RS::get_singleton()->texture_2d_create(image); + RS::get_singleton()->texture_replace(_texture, new_texture); + } else { + RS::get_singleton()->texture_2d_update(_texture, image); + } } else { _texture = RS::get_singleton()->texture_2d_create(image); } + _current_width = _width; emit_changed(); } -Ref<Curve> CurveTexture::get_curve() const { - return _curve; +Ref<Curve> CurveXYZTexture::get_curve_x() const { + return _curve_x; } -RID CurveTexture::get_rid() const { +Ref<Curve> CurveXYZTexture::get_curve_y() const { + return _curve_y; +} + +Ref<Curve> CurveXYZTexture::get_curve_z() const { + return _curve_z; +} + +RID CurveXYZTexture::get_rid() const { if (!_texture.is_valid()) { _texture = RS::get_singleton()->texture_2d_placeholder_create(); } return _texture; } -CurveTexture::CurveTexture() {} +CurveXYZTexture::CurveXYZTexture() {} -CurveTexture::~CurveTexture() { +CurveXYZTexture::~CurveXYZTexture() { if (_texture.is_valid()) { RS::get_singleton()->free(_texture); } @@ -1706,7 +1762,7 @@ void GradientTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("_update"), &GradientTexture::_update); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gradient", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_gradient", "get_gradient"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,2048,1,or_greater"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,4096"), "set_width", "get_width"); } void GradientTexture::set_gradient(Ref<Gradient> p_gradient) { @@ -1774,6 +1830,7 @@ void GradientTexture::_update() { } void GradientTexture::set_width(int p_width) { + ERR_FAIL_COND(p_width <= 0); width = p_width; _queue_update(); } @@ -1782,7 +1839,7 @@ int GradientTexture::get_width() const { return width; } -Ref<Image> GradientTexture::get_data() const { +Ref<Image> GradientTexture::get_image() const { if (!texture.is_valid()) { return Ref<Image>(); } @@ -2031,14 +2088,14 @@ bool AnimatedTexture::has_alpha() const { return frames[current_frame].texture->has_alpha(); } -Ref<Image> AnimatedTexture::get_data() const { +Ref<Image> AnimatedTexture::get_image() const { RWLockRead r(rw_lock); if (!frames[current_frame].texture.is_valid()) { return Ref<Image>(); } - return frames[current_frame].texture->get_data(); + return frames[current_frame].texture->get_image(); } bool AnimatedTexture::is_pixel_opaque(int p_x, int p_y) const { @@ -2055,7 +2112,7 @@ void AnimatedTexture::_validate_property(PropertyInfo &property) const { if (prop.begins_with("frame_")) { int frame = prop.get_slicec('/', 0).get_slicec('_', 1).to_int(); if (frame >= frame_count) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } } } @@ -2083,7 +2140,7 @@ void AnimatedTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("get_frame_delay", "frame"), &AnimatedTexture::get_frame_delay); ADD_PROPERTY(PropertyInfo(Variant::INT, "frames", PROPERTY_HINT_RANGE, "1," + itos(MAX_FRAMES), PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_frames", "get_frames"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "current_frame", PROPERTY_HINT_NONE, "", 0), "set_current_frame", "get_current_frame"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "current_frame", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_current_frame", "get_current_frame"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pause"), "set_pause", "get_pause"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "oneshot"), "set_oneshot", "get_oneshot"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fps", PROPERTY_HINT_RANGE, "0,1024,0.1"), "set_fps", "get_fps"); @@ -2436,15 +2493,15 @@ RES ResourceFormatLoaderStreamTextureLayered::load(const String &p_path, const S Ref<StreamTextureLayered> st; if (p_path.get_extension().to_lower() == "stexarray") { Ref<StreamTexture2DArray> s; - s.instance(); + s.instantiate(); st = s; } else if (p_path.get_extension().to_lower() == "scube") { Ref<StreamCubemap> s; - s.instance(); + s.instantiate(); st = s; } else if (p_path.get_extension().to_lower() == "scubearray") { Ref<StreamCubemapArray> s; - s.instance(); + s.instantiate(); st = s; } else { if (r_error) { @@ -2543,7 +2600,7 @@ uint32_t CameraTexture::get_flags() const { return 0; } -Ref<Image> CameraTexture::get_data() const { +Ref<Image> CameraTexture::get_image() const { // not (yet) supported return Ref<Image>(); } diff --git a/scene/resources/texture.h b/scene/resources/texture.h index a0d917fd86..98aa61138d 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -31,10 +31,10 @@ #ifndef TEXTURE_H #define TEXTURE_H +#include "core/io/file_access.h" #include "core/io/resource.h" #include "core/io/resource_loader.h" #include "core/math/rect2.h" -#include "core/os/file_access.h" #include "core/os/mutex.h" #include "core/os/rw_lock.h" #include "core/os/thread_safe.h" @@ -71,7 +71,7 @@ public: virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = true) const; virtual bool get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const; - virtual Ref<Image> get_data() const { return Ref<Image>(); } + virtual Ref<Image> get_image() const { return Ref<Image>(); } Texture2D(); }; @@ -107,8 +107,8 @@ public: Image::Format get_format() const; - void update(const Ref<Image> &p_image, bool p_immediate = false); - Ref<Image> get_data() const override; + void update(const Ref<Image> &p_image); + Ref<Image> get_image() const override; int get_width() const override; int get_height() const override; @@ -136,8 +136,8 @@ class StreamTexture2D : public Texture2D { public: enum DataFormat { DATA_FORMAT_IMAGE, - DATA_FORMAT_LOSSLESS, - DATA_FORMAT_LOSSY, + DATA_FORMAT_PNG, + DATA_FORMAT_WEBP, DATA_FORMAT_BASIS_UNIVERSAL, }; @@ -146,9 +146,6 @@ public: }; enum FormatBits { - FORMAT_MASK_IMAGE_FORMAT = (1 << 20) - 1, - FORMAT_BIT_LOSSLESS = 1 << 20, - FORMAT_BIT_LOSSY = 1 << 21, FORMAT_BIT_STREAM = 1 << 22, FORMAT_BIT_HAS_MIPMAPS = 1 << 23, FORMAT_BIT_DETECT_3D = 1 << 24, @@ -158,7 +155,7 @@ public: }; private: - Error _load_data(const String &p_path, int &tw, int &th, int &tw_custom, int &th_custom, Ref<Image> &image, bool &r_request_3d, bool &r_request_normal, bool &r_request_roughness, int &mipmap_limit, int p_size_limit = 0); + Error _load_data(const String &p_path, int &r_width, int &r_height, Ref<Image> &image, bool &r_request_3d, bool &r_request_normal, bool &r_request_roughness, int &mipmap_limit, int p_size_limit = 0); String path_to_file; mutable RID texture; Image::Format format = Image::FORMAT_MAX; @@ -203,7 +200,7 @@ public: virtual bool has_alpha() const override; bool is_pixel_opaque(int p_x, int p_y) const override; - virtual Ref<Image> get_data() const override; + virtual Ref<Image> get_image() const override; StreamTexture2D(); ~StreamTexture2D(); @@ -297,51 +294,6 @@ public: MeshTexture(); }; -class LargeTexture : public Texture2D { - GDCLASS(LargeTexture, Texture2D); - RES_BASE_EXTENSION("largetex"); - -protected: - struct Piece { - Point2 offset; - Ref<Texture2D> texture; - }; - - Vector<Piece> pieces; - Size2i size; - - Array _get_data() const; - void _set_data(const Array &p_array); - static void _bind_methods(); - -public: - virtual int get_width() const override; - virtual int get_height() const override; - virtual RID get_rid() const override; - - virtual bool has_alpha() const override; - - int add_piece(const Point2 &p_offset, const Ref<Texture2D> &p_texture); - void set_piece_offset(int p_idx, const Point2 &p_offset); - void set_piece_texture(int p_idx, const Ref<Texture2D> &p_texture); - - void set_size(const Size2 &p_size); - void clear(); - - int get_piece_count() const; - Vector2 get_piece_offset(int p_idx) const; - Ref<Texture2D> get_piece_texture(int p_idx) const; - Ref<Image> to_image() const; - - virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; - virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false) const override; - virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = true) const override; - - bool is_pixel_opaque(int p_x, int p_y) const override; - - LargeTexture(); -}; - class TextureLayered : public Texture { GDCLASS(TextureLayered, Texture); @@ -434,8 +386,8 @@ class StreamTextureLayered : public TextureLayered { public: enum DataFormat { DATA_FORMAT_IMAGE, - DATA_FORMAT_LOSSLESS, - DATA_FORMAT_LOSSY, + DATA_FORMAT_PNG, + DATA_FORMAT_WEBP, DATA_FORMAT_BASIS_UNIVERSAL, }; @@ -444,9 +396,6 @@ public: }; enum FormatBits { - FORMAT_MASK_IMAGE_FORMAT = (1 << 20) - 1, - FORMAT_BIT_LOSSLESS = 1 << 20, - FORMAT_BIT_LOSSY = 1 << 21, FORMAT_BIT_STREAM = 1 << 22, FORMAT_BIT_HAS_MIPMAPS = 1 << 23, }; @@ -577,8 +526,8 @@ class StreamTexture3D : public Texture3D { public: enum DataFormat { DATA_FORMAT_IMAGE, - DATA_FORMAT_LOSSLESS, - DATA_FORMAT_LOSSY, + DATA_FORMAT_PNG, + DATA_FORMAT_WEBP, DATA_FORMAT_BASIS_UNIVERSAL, }; @@ -587,9 +536,6 @@ public: }; enum FormatBits { - FORMAT_MASK_IMAGE_FORMAT = (1 << 20) - 1, - FORMAT_BIT_LOSSLESS = 1 << 20, - FORMAT_BIT_LOSSY = 1 << 21, FORMAT_BIT_STREAM = 1 << 22, FORMAT_BIT_HAS_MIPMAPS = 1 << 23, }; @@ -640,11 +586,19 @@ public: class CurveTexture : public Texture2D { GDCLASS(CurveTexture, Texture2D); RES_BASE_EXTENSION("curvetex") +public: + enum TextureMode { + TEXTURE_MODE_RGB, + TEXTURE_MODE_RED, + }; private: mutable RID _texture; Ref<Curve> _curve; int _width = 2048; + int _current_width = 0; + TextureMode texture_mode = TEXTURE_MODE_RGB; + TextureMode _current_texture_mode = TEXTURE_MODE_RGB; void _update(); @@ -655,6 +609,9 @@ public: void set_width(int p_width); int get_width() const override; + void set_texture_mode(TextureMode p_mode); + TextureMode get_texture_mode() const; + void ensure_default_setup(float p_min = 0, float p_max = 1); void set_curve(Ref<Curve> p_curve); @@ -668,18 +625,49 @@ public: CurveTexture(); ~CurveTexture(); }; -/* - enum CubeMapSide { - CUBEMAP_LEFT, - CUBEMAP_RIGHT, - CUBEMAP_BOTTOM, - CUBEMAP_TOP, - CUBEMAP_FRONT, - CUBEMAP_BACK, - }; -*/ -//VARIANT_ENUM_CAST( Texture::CubeMapSide ); +VARIANT_ENUM_CAST(CurveTexture::TextureMode) + +class CurveXYZTexture : public Texture2D { + GDCLASS(CurveXYZTexture, Texture2D); + RES_BASE_EXTENSION("curvetex") + +private: + mutable RID _texture; + Ref<Curve> _curve_x; + Ref<Curve> _curve_y; + Ref<Curve> _curve_z; + int _width = 2048; + int _current_width = 0; + + void _update(); + +protected: + static void _bind_methods(); + +public: + void set_width(int p_width); + int get_width() const override; + + void ensure_default_setup(float p_min = 0, float p_max = 1); + + void set_curve_x(Ref<Curve> p_curve); + Ref<Curve> get_curve_x() const; + + void set_curve_y(Ref<Curve> p_curve); + Ref<Curve> get_curve_y() const; + + void set_curve_z(Ref<Curve> p_curve); + Ref<Curve> get_curve_z() const; + + virtual RID get_rid() const override; + + virtual int get_height() const override { return 1; } + virtual bool has_alpha() const override { return false; } + + CurveXYZTexture(); + ~CurveXYZTexture(); +}; class GradientTexture : public Texture2D { GDCLASS(GradientTexture, Texture2D); @@ -716,7 +704,7 @@ public: virtual int get_height() const override { return 1; } virtual bool has_alpha() const override { return true; } - virtual Ref<Image> get_data() const override; + virtual Ref<Image> get_image() const override; GradientTexture(); virtual ~GradientTexture(); @@ -812,7 +800,7 @@ public: virtual bool has_alpha() const override; - virtual Ref<Image> get_data() const override; + virtual Ref<Image> get_image() const override; bool is_pixel_opaque(int p_x, int p_y) const override; @@ -839,7 +827,7 @@ public: virtual void set_flags(uint32_t p_flags); virtual uint32_t get_flags() const; - virtual Ref<Image> get_data() const override; + virtual Ref<Image> get_image() const override; void set_camera_feed_id(int p_new_id); int get_camera_feed_id() const; diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp index 2b173add38..303bbf38f4 100644 --- a/scene/resources/theme.cpp +++ b/scene/resources/theme.cpp @@ -29,18 +29,23 @@ /*************************************************************************/ #include "theme.h" -#include "core/os/file_access.h" +#include "core/io/file_access.h" #include "core/string/print_string.h" void Theme::_emit_theme_changed() { + if (no_change_propagation) { + return; + } + + notify_property_list_changed(); emit_changed(); } -Vector<String> Theme::_get_icon_list(const String &p_node_type) const { +Vector<String> Theme::_get_icon_list(const String &p_theme_type) const { Vector<String> ilret; List<StringName> il; - get_icon_list(p_node_type, &il); + get_icon_list(p_theme_type, &il); ilret.resize(il.size()); int i = 0; @@ -66,11 +71,11 @@ Vector<String> Theme::_get_icon_type_list() const { return ilret; } -Vector<String> Theme::_get_stylebox_list(const String &p_node_type) const { +Vector<String> Theme::_get_stylebox_list(const String &p_theme_type) const { Vector<String> ilret; List<StringName> il; - get_stylebox_list(p_node_type, &il); + get_stylebox_list(p_theme_type, &il); ilret.resize(il.size()); int i = 0; @@ -96,11 +101,11 @@ Vector<String> Theme::_get_stylebox_type_list() const { return ilret; } -Vector<String> Theme::_get_font_list(const String &p_node_type) const { +Vector<String> Theme::_get_font_list(const String &p_theme_type) const { Vector<String> ilret; List<StringName> il; - get_font_list(p_node_type, &il); + get_font_list(p_theme_type, &il); ilret.resize(il.size()); int i = 0; @@ -126,11 +131,11 @@ Vector<String> Theme::_get_font_type_list() const { return ilret; } -Vector<String> Theme::_get_font_size_list(const String &p_node_type) const { +Vector<String> Theme::_get_font_size_list(const String &p_theme_type) const { Vector<String> ilret; List<StringName> il; - get_font_size_list(p_node_type, &il); + get_font_size_list(p_theme_type, &il); ilret.resize(il.size()); int i = 0; @@ -156,11 +161,11 @@ Vector<String> Theme::_get_font_size_type_list() const { return ilret; } -Vector<String> Theme::_get_color_list(const String &p_node_type) const { +Vector<String> Theme::_get_color_list(const String &p_theme_type) const { Vector<String> ilret; List<StringName> il; - get_color_list(p_node_type, &il); + get_color_list(p_theme_type, &il); ilret.resize(il.size()); int i = 0; @@ -186,11 +191,11 @@ Vector<String> Theme::_get_color_type_list() const { return ilret; } -Vector<String> Theme::_get_constant_list(const String &p_node_type) const { +Vector<String> Theme::_get_constant_list(const String &p_theme_type) const { Vector<String> ilret; List<StringName> il; - get_constant_list(p_node_type, &il); + get_constant_list(p_theme_type, &il); ilret.resize(il.size()); int i = 0; @@ -216,20 +221,20 @@ Vector<String> Theme::_get_constant_type_list() const { return ilret; } -Vector<String> Theme::_get_theme_item_list(DataType p_data_type, const String &p_node_type) const { +Vector<String> Theme::_get_theme_item_list(DataType p_data_type, const String &p_theme_type) const { switch (p_data_type) { case DATA_TYPE_COLOR: - return _get_color_list(p_node_type); + return _get_color_list(p_theme_type); case DATA_TYPE_CONSTANT: - return _get_constant_list(p_node_type); + return _get_constant_list(p_theme_type); case DATA_TYPE_FONT: - return _get_font_list(p_node_type); + return _get_font_list(p_theme_type); case DATA_TYPE_FONT_SIZE: - return _get_font_size_list(p_node_type); + return _get_font_size_list(p_theme_type); case DATA_TYPE_ICON: - return _get_icon_list(p_node_type); + return _get_icon_list(p_theme_type); case DATA_TYPE_STYLEBOX: - return _get_stylebox_list(p_node_type); + return _get_stylebox_list(p_theme_type); case DATA_TYPE_MAX: break; // Can't happen, but silences warning. } @@ -258,6 +263,21 @@ Vector<String> Theme::_get_theme_item_type_list(DataType p_data_type) const { return Vector<String>(); } +Vector<String> Theme::_get_type_variation_list(const StringName &p_theme_type) const { + Vector<String> ilret; + List<StringName> il; + + get_type_variation_list(p_theme_type, &il); + ilret.resize(il.size()); + + int i = 0; + String *w = ilret.ptrw(); + for (List<StringName>::Element *E = il.front(); E; E = E->next(), i++) { + w[i] = E->get(); + } + return ilret; +} + Vector<String> Theme::_get_type_list() const { Vector<String> ilret; List<StringName> il; @@ -278,19 +298,23 @@ bool Theme::_set(const StringName &p_name, const Variant &p_value) { if (sname.find("/") != -1) { String type = sname.get_slicec('/', 1); - String node_type = sname.get_slicec('/', 0); + String theme_type = sname.get_slicec('/', 0); String name = sname.get_slicec('/', 2); if (type == "icons") { - set_icon(name, node_type, p_value); + set_icon(name, theme_type, p_value); } else if (type == "styles") { - set_stylebox(name, node_type, p_value); + set_stylebox(name, theme_type, p_value); } else if (type == "fonts") { - set_font(name, node_type, p_value); + set_font(name, theme_type, p_value); + } else if (type == "font_sizes") { + set_font_size(name, theme_type, p_value); } else if (type == "colors") { - set_color(name, node_type, p_value); + set_color(name, theme_type, p_value); } else if (type == "constants") { - set_constant(name, node_type, p_value); + set_constant(name, theme_type, p_value); + } else if (type == "base_type") { + set_type_variation(theme_type, p_value); } else { return false; } @@ -306,31 +330,35 @@ bool Theme::_get(const StringName &p_name, Variant &r_ret) const { if (sname.find("/") != -1) { String type = sname.get_slicec('/', 1); - String node_type = sname.get_slicec('/', 0); + String theme_type = sname.get_slicec('/', 0); String name = sname.get_slicec('/', 2); if (type == "icons") { - if (!has_icon(name, node_type)) { + if (!has_icon(name, theme_type)) { r_ret = Ref<Texture2D>(); } else { - r_ret = get_icon(name, node_type); + r_ret = get_icon(name, theme_type); } } else if (type == "styles") { - if (!has_stylebox(name, node_type)) { + if (!has_stylebox(name, theme_type)) { r_ret = Ref<StyleBox>(); } else { - r_ret = get_stylebox(name, node_type); + r_ret = get_stylebox(name, theme_type); } } else if (type == "fonts") { - if (!has_font(name, node_type)) { + if (!has_font(name, theme_type)) { r_ret = Ref<Font>(); } else { - r_ret = get_font(name, node_type); + r_ret = get_font(name, theme_type); } + } else if (type == "font_sizes") { + r_ret = get_font_size(name, theme_type); } else if (type == "colors") { - r_ret = get_color(name, node_type); + r_ret = get_color(name, theme_type); } else if (type == "constants") { - r_ret = get_constant(name, node_type); + r_ret = get_constant(name, theme_type); + } else if (type == "base_type") { + r_ret = get_type_variation_base(theme_type); } else { return false; } @@ -346,6 +374,14 @@ void Theme::_get_property_list(List<PropertyInfo> *p_list) const { const StringName *key = nullptr; + // Type variations. + while ((key = variation_map.next(key))) { + list.push_back(PropertyInfo(Variant::STRING_NAME, String() + *key + "/base_type")); + } + + key = nullptr; + + // Icons. while ((key = icon_map.next(key))) { const StringName *key2 = nullptr; @@ -356,6 +392,7 @@ void Theme::_get_property_list(List<PropertyInfo> *p_list) const { key = nullptr; + // Styles. while ((key = style_map.next(key))) { const StringName *key2 = nullptr; @@ -366,6 +403,7 @@ void Theme::_get_property_list(List<PropertyInfo> *p_list) const { key = nullptr; + // Fonts. while ((key = font_map.next(key))) { const StringName *key2 = nullptr; @@ -376,6 +414,18 @@ void Theme::_get_property_list(List<PropertyInfo> *p_list) const { key = nullptr; + // Font sizes. + while ((key = font_size_map.next(key))) { + const StringName *key2 = nullptr; + + while ((key2 = font_size_map[*key].next(key2))) { + list.push_back(PropertyInfo(Variant::INT, String() + *key + "/font_sizes/" + *key2)); + } + } + + key = nullptr; + + // Colors. while ((key = color_map.next(key))) { const StringName *key2 = nullptr; @@ -386,6 +436,7 @@ void Theme::_get_property_list(List<PropertyInfo> *p_list) const { key = nullptr; + // Constants. while ((key = constant_map.next(key))) { const StringName *key2 = nullptr; @@ -394,6 +445,7 @@ void Theme::_get_property_list(List<PropertyInfo> *p_list) const { } } + // Sort and store properties. list.sort(); for (List<PropertyInfo>::Element *E = list.front(); E; E = E->next()) { p_list->push_back(E->get()); @@ -415,8 +467,7 @@ void Theme::set_default_theme_font(const Ref<Font> &p_default_font) { default_theme_font->connect("changed", callable_mp(this, &Theme::_emit_theme_changed), varray(), CONNECT_REFERENCE_COUNTED); } - notify_property_list_changed(); - emit_changed(); + _emit_theme_changed(); } Ref<Font> Theme::get_default_theme_font() const { @@ -430,8 +481,7 @@ void Theme::set_default_theme_font_size(int p_font_size) { default_theme_font_size = p_font_size; - notify_property_list_changed(); - emit_changed(); + _emit_theme_changed(); } int Theme::get_default_theme_font_size() const { @@ -477,79 +527,79 @@ void Theme::set_default_font_size(int p_font_size) { default_font_size = p_font_size; } -void Theme::set_icon(const StringName &p_name, const StringName &p_node_type, const Ref<Texture2D> &p_icon) { - bool new_value = !icon_map.has(p_node_type) || !icon_map[p_node_type].has(p_name); - - if (icon_map[p_node_type].has(p_name) && icon_map[p_node_type][p_name].is_valid()) { - icon_map[p_node_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); +void Theme::set_icon(const StringName &p_name, const StringName &p_theme_type, const Ref<Texture2D> &p_icon) { + if (icon_map[p_theme_type].has(p_name) && icon_map[p_theme_type][p_name].is_valid()) { + icon_map[p_theme_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); } - icon_map[p_node_type][p_name] = p_icon; + icon_map[p_theme_type][p_name] = p_icon; if (p_icon.is_valid()) { - icon_map[p_node_type][p_name]->connect("changed", callable_mp(this, &Theme::_emit_theme_changed), varray(), CONNECT_REFERENCE_COUNTED); + icon_map[p_theme_type][p_name]->connect("changed", callable_mp(this, &Theme::_emit_theme_changed), varray(), CONNECT_REFERENCE_COUNTED); } - if (new_value) { - notify_property_list_changed(); - emit_changed(); - } + _emit_theme_changed(); } -Ref<Texture2D> Theme::get_icon(const StringName &p_name, const StringName &p_node_type) const { - if (icon_map.has(p_node_type) && icon_map[p_node_type].has(p_name) && icon_map[p_node_type][p_name].is_valid()) { - return icon_map[p_node_type][p_name]; +Ref<Texture2D> Theme::get_icon(const StringName &p_name, const StringName &p_theme_type) const { + if (icon_map.has(p_theme_type) && icon_map[p_theme_type].has(p_name) && icon_map[p_theme_type][p_name].is_valid()) { + return icon_map[p_theme_type][p_name]; } else { return default_icon; } } -bool Theme::has_icon(const StringName &p_name, const StringName &p_node_type) const { - return (icon_map.has(p_node_type) && icon_map[p_node_type].has(p_name) && icon_map[p_node_type][p_name].is_valid()); +bool Theme::has_icon(const StringName &p_name, const StringName &p_theme_type) const { + return (icon_map.has(p_theme_type) && icon_map[p_theme_type].has(p_name) && icon_map[p_theme_type][p_name].is_valid()); +} + +bool Theme::has_icon_nocheck(const StringName &p_name, const StringName &p_theme_type) const { + return (icon_map.has(p_theme_type) && icon_map[p_theme_type].has(p_name)); } -void Theme::rename_icon(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type) { - ERR_FAIL_COND_MSG(!icon_map.has(p_node_type), "Cannot rename the icon '" + String(p_old_name) + "' because the node type '" + String(p_node_type) + "' does not exist."); - ERR_FAIL_COND_MSG(icon_map[p_node_type].has(p_name), "Cannot rename the icon '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); - ERR_FAIL_COND_MSG(!icon_map[p_node_type].has(p_old_name), "Cannot rename the icon '" + String(p_old_name) + "' because it does not exist."); +void Theme::rename_icon(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!icon_map.has(p_theme_type), "Cannot rename the icon '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); + ERR_FAIL_COND_MSG(icon_map[p_theme_type].has(p_name), "Cannot rename the icon '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); + ERR_FAIL_COND_MSG(!icon_map[p_theme_type].has(p_old_name), "Cannot rename the icon '" + String(p_old_name) + "' because it does not exist."); - icon_map[p_node_type][p_name] = icon_map[p_node_type][p_old_name]; - icon_map[p_node_type].erase(p_old_name); + icon_map[p_theme_type][p_name] = icon_map[p_theme_type][p_old_name]; + icon_map[p_theme_type].erase(p_old_name); - notify_property_list_changed(); - emit_changed(); + _emit_theme_changed(); } -void Theme::clear_icon(const StringName &p_name, const StringName &p_node_type) { - ERR_FAIL_COND_MSG(!icon_map.has(p_node_type), "Cannot clear the icon '" + String(p_name) + "' because the node type '" + String(p_node_type) + "' does not exist."); - ERR_FAIL_COND_MSG(!icon_map[p_node_type].has(p_name), "Cannot clear the icon '" + String(p_name) + "' because it does not exist."); +void Theme::clear_icon(const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!icon_map.has(p_theme_type), "Cannot clear the icon '" + String(p_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); + ERR_FAIL_COND_MSG(!icon_map[p_theme_type].has(p_name), "Cannot clear the icon '" + String(p_name) + "' because it does not exist."); - if (icon_map[p_node_type][p_name].is_valid()) { - icon_map[p_node_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); + if (icon_map[p_theme_type][p_name].is_valid()) { + icon_map[p_theme_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); } - icon_map[p_node_type].erase(p_name); + icon_map[p_theme_type].erase(p_name); - notify_property_list_changed(); - emit_changed(); + _emit_theme_changed(); } -void Theme::get_icon_list(StringName p_node_type, List<StringName> *p_list) const { +void Theme::get_icon_list(StringName p_theme_type, List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); - if (!icon_map.has(p_node_type)) { + if (!icon_map.has(p_theme_type)) { return; } const StringName *key = nullptr; - while ((key = icon_map[p_node_type].next(key))) { + while ((key = icon_map[p_theme_type].next(key))) { p_list->push_back(*key); } } -void Theme::add_icon_type(const StringName &p_node_type) { - icon_map[p_node_type] = HashMap<StringName, Ref<Texture2D>>(); +void Theme::add_icon_type(const StringName &p_theme_type) { + if (icon_map.has(p_theme_type)) { + return; + } + icon_map[p_theme_type] = HashMap<StringName, Ref<Texture2D>>(); } void Theme::get_icon_type_list(List<StringName> *p_list) const { @@ -561,79 +611,79 @@ void Theme::get_icon_type_list(List<StringName> *p_list) const { } } -void Theme::set_stylebox(const StringName &p_name, const StringName &p_node_type, const Ref<StyleBox> &p_style) { - bool new_value = !style_map.has(p_node_type) || !style_map[p_node_type].has(p_name); - - if (style_map[p_node_type].has(p_name) && style_map[p_node_type][p_name].is_valid()) { - style_map[p_node_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); +void Theme::set_stylebox(const StringName &p_name, const StringName &p_theme_type, const Ref<StyleBox> &p_style) { + if (style_map[p_theme_type].has(p_name) && style_map[p_theme_type][p_name].is_valid()) { + style_map[p_theme_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); } - style_map[p_node_type][p_name] = p_style; + style_map[p_theme_type][p_name] = p_style; if (p_style.is_valid()) { - style_map[p_node_type][p_name]->connect("changed", callable_mp(this, &Theme::_emit_theme_changed), varray(), CONNECT_REFERENCE_COUNTED); + style_map[p_theme_type][p_name]->connect("changed", callable_mp(this, &Theme::_emit_theme_changed), varray(), CONNECT_REFERENCE_COUNTED); } - if (new_value) { - notify_property_list_changed(); - } - emit_changed(); + _emit_theme_changed(); } -Ref<StyleBox> Theme::get_stylebox(const StringName &p_name, const StringName &p_node_type) const { - if (style_map.has(p_node_type) && style_map[p_node_type].has(p_name) && style_map[p_node_type][p_name].is_valid()) { - return style_map[p_node_type][p_name]; +Ref<StyleBox> Theme::get_stylebox(const StringName &p_name, const StringName &p_theme_type) const { + if (style_map.has(p_theme_type) && style_map[p_theme_type].has(p_name) && style_map[p_theme_type][p_name].is_valid()) { + return style_map[p_theme_type][p_name]; } else { return default_style; } } -bool Theme::has_stylebox(const StringName &p_name, const StringName &p_node_type) const { - return (style_map.has(p_node_type) && style_map[p_node_type].has(p_name) && style_map[p_node_type][p_name].is_valid()); +bool Theme::has_stylebox(const StringName &p_name, const StringName &p_theme_type) const { + return (style_map.has(p_theme_type) && style_map[p_theme_type].has(p_name) && style_map[p_theme_type][p_name].is_valid()); +} + +bool Theme::has_stylebox_nocheck(const StringName &p_name, const StringName &p_theme_type) const { + return (style_map.has(p_theme_type) && style_map[p_theme_type].has(p_name)); } -void Theme::rename_stylebox(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type) { - ERR_FAIL_COND_MSG(!style_map.has(p_node_type), "Cannot rename the stylebox '" + String(p_old_name) + "' because the node type '" + String(p_node_type) + "' does not exist."); - ERR_FAIL_COND_MSG(style_map[p_node_type].has(p_name), "Cannot rename the stylebox '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); - ERR_FAIL_COND_MSG(!style_map[p_node_type].has(p_old_name), "Cannot rename the stylebox '" + String(p_old_name) + "' because it does not exist."); +void Theme::rename_stylebox(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!style_map.has(p_theme_type), "Cannot rename the stylebox '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); + ERR_FAIL_COND_MSG(style_map[p_theme_type].has(p_name), "Cannot rename the stylebox '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); + ERR_FAIL_COND_MSG(!style_map[p_theme_type].has(p_old_name), "Cannot rename the stylebox '" + String(p_old_name) + "' because it does not exist."); - style_map[p_node_type][p_name] = style_map[p_node_type][p_old_name]; - style_map[p_node_type].erase(p_old_name); + style_map[p_theme_type][p_name] = style_map[p_theme_type][p_old_name]; + style_map[p_theme_type].erase(p_old_name); - notify_property_list_changed(); - emit_changed(); + _emit_theme_changed(); } -void Theme::clear_stylebox(const StringName &p_name, const StringName &p_node_type) { - ERR_FAIL_COND_MSG(!style_map.has(p_node_type), "Cannot clear the stylebox '" + String(p_name) + "' because the node type '" + String(p_node_type) + "' does not exist."); - ERR_FAIL_COND_MSG(!style_map[p_node_type].has(p_name), "Cannot clear the stylebox '" + String(p_name) + "' because it does not exist."); +void Theme::clear_stylebox(const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!style_map.has(p_theme_type), "Cannot clear the stylebox '" + String(p_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); + ERR_FAIL_COND_MSG(!style_map[p_theme_type].has(p_name), "Cannot clear the stylebox '" + String(p_name) + "' because it does not exist."); - if (style_map[p_node_type][p_name].is_valid()) { - style_map[p_node_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); + if (style_map[p_theme_type][p_name].is_valid()) { + style_map[p_theme_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); } - style_map[p_node_type].erase(p_name); + style_map[p_theme_type].erase(p_name); - notify_property_list_changed(); - emit_changed(); + _emit_theme_changed(); } -void Theme::get_stylebox_list(StringName p_node_type, List<StringName> *p_list) const { +void Theme::get_stylebox_list(StringName p_theme_type, List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); - if (!style_map.has(p_node_type)) { + if (!style_map.has(p_theme_type)) { return; } const StringName *key = nullptr; - while ((key = style_map[p_node_type].next(key))) { + while ((key = style_map[p_theme_type].next(key))) { p_list->push_back(*key); } } -void Theme::add_stylebox_type(const StringName &p_node_type) { - style_map[p_node_type] = HashMap<StringName, Ref<StyleBox>>(); +void Theme::add_stylebox_type(const StringName &p_theme_type) { + if (style_map.has(p_theme_type)) { + return; + } + style_map[p_theme_type] = HashMap<StringName, Ref<StyleBox>>(); } void Theme::get_stylebox_type_list(List<StringName> *p_list) const { @@ -645,28 +695,23 @@ void Theme::get_stylebox_type_list(List<StringName> *p_list) const { } } -void Theme::set_font(const StringName &p_name, const StringName &p_node_type, const Ref<Font> &p_font) { - bool new_value = !font_map.has(p_node_type) || !font_map[p_node_type].has(p_name); - - if (font_map[p_node_type][p_name].is_valid()) { - font_map[p_node_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); +void Theme::set_font(const StringName &p_name, const StringName &p_theme_type, const Ref<Font> &p_font) { + if (font_map[p_theme_type][p_name].is_valid()) { + font_map[p_theme_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); } - font_map[p_node_type][p_name] = p_font; + font_map[p_theme_type][p_name] = p_font; if (p_font.is_valid()) { - font_map[p_node_type][p_name]->connect("changed", callable_mp(this, &Theme::_emit_theme_changed), varray(), CONNECT_REFERENCE_COUNTED); + font_map[p_theme_type][p_name]->connect("changed", callable_mp(this, &Theme::_emit_theme_changed), varray(), CONNECT_REFERENCE_COUNTED); } - if (new_value) { - notify_property_list_changed(); - emit_changed(); - } + _emit_theme_changed(); } -Ref<Font> Theme::get_font(const StringName &p_name, const StringName &p_node_type) const { - if (font_map.has(p_node_type) && font_map[p_node_type].has(p_name) && font_map[p_node_type][p_name].is_valid()) { - return font_map[p_node_type][p_name]; +Ref<Font> Theme::get_font(const StringName &p_name, const StringName &p_theme_type) const { + if (font_map.has(p_theme_type) && font_map[p_theme_type].has(p_name) && font_map[p_theme_type][p_name].is_valid()) { + return font_map[p_theme_type][p_name]; } else if (default_theme_font.is_valid()) { return default_theme_font; } else { @@ -674,51 +719,57 @@ Ref<Font> Theme::get_font(const StringName &p_name, const StringName &p_node_typ } } -bool Theme::has_font(const StringName &p_name, const StringName &p_node_type) const { - return ((font_map.has(p_node_type) && font_map[p_node_type].has(p_name) && font_map[p_node_type][p_name].is_valid()) || default_theme_font.is_valid()); +bool Theme::has_font(const StringName &p_name, const StringName &p_theme_type) const { + return ((font_map.has(p_theme_type) && font_map[p_theme_type].has(p_name) && font_map[p_theme_type][p_name].is_valid()) || default_theme_font.is_valid()); } -void Theme::rename_font(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type) { - ERR_FAIL_COND_MSG(!font_map.has(p_node_type), "Cannot rename the font '" + String(p_old_name) + "' because the node type '" + String(p_node_type) + "' does not exist."); - ERR_FAIL_COND_MSG(font_map[p_node_type].has(p_name), "Cannot rename the font '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); - ERR_FAIL_COND_MSG(!font_map[p_node_type].has(p_old_name), "Cannot rename the font '" + String(p_old_name) + "' because it does not exist."); +bool Theme::has_font_nocheck(const StringName &p_name, const StringName &p_theme_type) const { + return (font_map.has(p_theme_type) && font_map[p_theme_type].has(p_name)); +} - font_map[p_node_type][p_name] = font_map[p_node_type][p_old_name]; - font_map[p_node_type].erase(p_old_name); +void Theme::rename_font(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!font_map.has(p_theme_type), "Cannot rename the font '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); + ERR_FAIL_COND_MSG(font_map[p_theme_type].has(p_name), "Cannot rename the font '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); + ERR_FAIL_COND_MSG(!font_map[p_theme_type].has(p_old_name), "Cannot rename the font '" + String(p_old_name) + "' because it does not exist."); - notify_property_list_changed(); - emit_changed(); + font_map[p_theme_type][p_name] = font_map[p_theme_type][p_old_name]; + font_map[p_theme_type].erase(p_old_name); + + _emit_theme_changed(); } -void Theme::clear_font(const StringName &p_name, const StringName &p_node_type) { - ERR_FAIL_COND_MSG(!font_map.has(p_node_type), "Cannot clear the font '" + String(p_name) + "' because the node type '" + String(p_node_type) + "' does not exist."); - ERR_FAIL_COND_MSG(!font_map[p_node_type].has(p_name), "Cannot clear the font '" + String(p_name) + "' because it does not exist."); +void Theme::clear_font(const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!font_map.has(p_theme_type), "Cannot clear the font '" + String(p_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); + ERR_FAIL_COND_MSG(!font_map[p_theme_type].has(p_name), "Cannot clear the font '" + String(p_name) + "' because it does not exist."); - if (font_map[p_node_type][p_name].is_valid()) { - font_map[p_node_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); + if (font_map[p_theme_type][p_name].is_valid()) { + font_map[p_theme_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed)); } - font_map[p_node_type].erase(p_name); - notify_property_list_changed(); - emit_changed(); + font_map[p_theme_type].erase(p_name); + + _emit_theme_changed(); } -void Theme::get_font_list(StringName p_node_type, List<StringName> *p_list) const { +void Theme::get_font_list(StringName p_theme_type, List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); - if (!font_map.has(p_node_type)) { + if (!font_map.has(p_theme_type)) { return; } const StringName *key = nullptr; - while ((key = font_map[p_node_type].next(key))) { + while ((key = font_map[p_theme_type].next(key))) { p_list->push_back(*key); } } -void Theme::add_font_type(const StringName &p_node_type) { - font_map[p_node_type] = HashMap<StringName, Ref<Font>>(); +void Theme::add_font_type(const StringName &p_theme_type) { + if (font_map.has(p_theme_type)) { + return; + } + font_map[p_theme_type] = HashMap<StringName, Ref<Font>>(); } void Theme::get_font_type_list(List<StringName> *p_list) const { @@ -730,20 +781,15 @@ void Theme::get_font_type_list(List<StringName> *p_list) const { } } -void Theme::set_font_size(const StringName &p_name, const StringName &p_node_type, int p_font_size) { - bool new_value = !font_size_map.has(p_node_type) || !font_size_map[p_node_type].has(p_name); - - font_size_map[p_node_type][p_name] = p_font_size; +void Theme::set_font_size(const StringName &p_name, const StringName &p_theme_type, int p_font_size) { + font_size_map[p_theme_type][p_name] = p_font_size; - if (new_value) { - notify_property_list_changed(); - emit_changed(); - } + _emit_theme_changed(); } -int Theme::get_font_size(const StringName &p_name, const StringName &p_node_type) const { - if (font_size_map.has(p_node_type) && font_size_map[p_node_type].has(p_name) && (font_size_map[p_node_type][p_name] > 0)) { - return font_size_map[p_node_type][p_name]; +int Theme::get_font_size(const StringName &p_name, const StringName &p_theme_type) const { + if (font_size_map.has(p_theme_type) && font_size_map[p_theme_type].has(p_name) && (font_size_map[p_theme_type][p_name] > 0)) { + return font_size_map[p_theme_type][p_name]; } else if (default_theme_font_size > 0) { return default_theme_font_size; } else { @@ -751,47 +797,53 @@ int Theme::get_font_size(const StringName &p_name, const StringName &p_node_type } } -bool Theme::has_font_size(const StringName &p_name, const StringName &p_node_type) const { - return ((font_size_map.has(p_node_type) && font_size_map[p_node_type].has(p_name) && (font_size_map[p_node_type][p_name] > 0)) || (default_theme_font_size > 0)); +bool Theme::has_font_size(const StringName &p_name, const StringName &p_theme_type) const { + return ((font_size_map.has(p_theme_type) && font_size_map[p_theme_type].has(p_name) && (font_size_map[p_theme_type][p_name] > 0)) || (default_theme_font_size > 0)); } -void Theme::rename_font_size(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type) { - ERR_FAIL_COND_MSG(!font_size_map.has(p_node_type), "Cannot rename the font size '" + String(p_old_name) + "' because the node type '" + String(p_node_type) + "' does not exist."); - ERR_FAIL_COND_MSG(font_size_map[p_node_type].has(p_name), "Cannot rename the font size '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); - ERR_FAIL_COND_MSG(!font_size_map[p_node_type].has(p_old_name), "Cannot rename the font size '" + String(p_old_name) + "' because it does not exist."); +bool Theme::has_font_size_nocheck(const StringName &p_name, const StringName &p_theme_type) const { + return (font_size_map.has(p_theme_type) && font_size_map[p_theme_type].has(p_name)); +} - font_size_map[p_node_type][p_name] = font_size_map[p_node_type][p_old_name]; - font_size_map[p_node_type].erase(p_old_name); +void Theme::rename_font_size(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!font_size_map.has(p_theme_type), "Cannot rename the font size '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); + ERR_FAIL_COND_MSG(font_size_map[p_theme_type].has(p_name), "Cannot rename the font size '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); + ERR_FAIL_COND_MSG(!font_size_map[p_theme_type].has(p_old_name), "Cannot rename the font size '" + String(p_old_name) + "' because it does not exist."); - notify_property_list_changed(); - emit_changed(); + font_size_map[p_theme_type][p_name] = font_size_map[p_theme_type][p_old_name]; + font_size_map[p_theme_type].erase(p_old_name); + + _emit_theme_changed(); } -void Theme::clear_font_size(const StringName &p_name, const StringName &p_node_type) { - ERR_FAIL_COND_MSG(!font_size_map.has(p_node_type), "Cannot clear the font size '" + String(p_name) + "' because the node type '" + String(p_node_type) + "' does not exist."); - ERR_FAIL_COND_MSG(!font_size_map[p_node_type].has(p_name), "Cannot clear the font size '" + String(p_name) + "' because it does not exist."); +void Theme::clear_font_size(const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!font_size_map.has(p_theme_type), "Cannot clear the font size '" + String(p_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); + ERR_FAIL_COND_MSG(!font_size_map[p_theme_type].has(p_name), "Cannot clear the font size '" + String(p_name) + "' because it does not exist."); - font_size_map[p_node_type].erase(p_name); - notify_property_list_changed(); - emit_changed(); + font_size_map[p_theme_type].erase(p_name); + + _emit_theme_changed(); } -void Theme::get_font_size_list(StringName p_node_type, List<StringName> *p_list) const { +void Theme::get_font_size_list(StringName p_theme_type, List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); - if (!font_size_map.has(p_node_type)) { + if (!font_size_map.has(p_theme_type)) { return; } const StringName *key = nullptr; - while ((key = font_size_map[p_node_type].next(key))) { + while ((key = font_size_map[p_theme_type].next(key))) { p_list->push_back(*key); } } -void Theme::add_font_size_type(const StringName &p_node_type) { - font_size_map[p_node_type] = HashMap<StringName, int>(); +void Theme::add_font_size_type(const StringName &p_theme_type) { + if (font_size_map.has(p_theme_type)) { + return; + } + font_size_map[p_theme_type] = HashMap<StringName, int>(); } void Theme::get_font_size_type_list(List<StringName> *p_list) const { @@ -803,66 +855,67 @@ void Theme::get_font_size_type_list(List<StringName> *p_list) const { } } -void Theme::set_color(const StringName &p_name, const StringName &p_node_type, const Color &p_color) { - bool new_value = !color_map.has(p_node_type) || !color_map[p_node_type].has(p_name); - - color_map[p_node_type][p_name] = p_color; +void Theme::set_color(const StringName &p_name, const StringName &p_theme_type, const Color &p_color) { + color_map[p_theme_type][p_name] = p_color; - if (new_value) { - notify_property_list_changed(); - emit_changed(); - } + _emit_theme_changed(); } -Color Theme::get_color(const StringName &p_name, const StringName &p_node_type) const { - if (color_map.has(p_node_type) && color_map[p_node_type].has(p_name)) { - return color_map[p_node_type][p_name]; +Color Theme::get_color(const StringName &p_name, const StringName &p_theme_type) const { + if (color_map.has(p_theme_type) && color_map[p_theme_type].has(p_name)) { + return color_map[p_theme_type][p_name]; } else { return Color(); } } -bool Theme::has_color(const StringName &p_name, const StringName &p_node_type) const { - return (color_map.has(p_node_type) && color_map[p_node_type].has(p_name)); +bool Theme::has_color(const StringName &p_name, const StringName &p_theme_type) const { + return (color_map.has(p_theme_type) && color_map[p_theme_type].has(p_name)); } -void Theme::rename_color(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type) { - ERR_FAIL_COND_MSG(!color_map.has(p_node_type), "Cannot rename the color '" + String(p_old_name) + "' because the node type '" + String(p_node_type) + "' does not exist."); - ERR_FAIL_COND_MSG(color_map[p_node_type].has(p_name), "Cannot rename the color '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); - ERR_FAIL_COND_MSG(!color_map[p_node_type].has(p_old_name), "Cannot rename the color '" + String(p_old_name) + "' because it does not exist."); +bool Theme::has_color_nocheck(const StringName &p_name, const StringName &p_theme_type) const { + return (color_map.has(p_theme_type) && color_map[p_theme_type].has(p_name)); +} - color_map[p_node_type][p_name] = color_map[p_node_type][p_old_name]; - color_map[p_node_type].erase(p_old_name); +void Theme::rename_color(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!color_map.has(p_theme_type), "Cannot rename the color '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); + ERR_FAIL_COND_MSG(color_map[p_theme_type].has(p_name), "Cannot rename the color '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); + ERR_FAIL_COND_MSG(!color_map[p_theme_type].has(p_old_name), "Cannot rename the color '" + String(p_old_name) + "' because it does not exist."); - notify_property_list_changed(); - emit_changed(); + color_map[p_theme_type][p_name] = color_map[p_theme_type][p_old_name]; + color_map[p_theme_type].erase(p_old_name); + + _emit_theme_changed(); } -void Theme::clear_color(const StringName &p_name, const StringName &p_node_type) { - ERR_FAIL_COND_MSG(!color_map.has(p_node_type), "Cannot clear the color '" + String(p_name) + "' because the node type '" + String(p_node_type) + "' does not exist."); - ERR_FAIL_COND_MSG(!color_map[p_node_type].has(p_name), "Cannot clear the color '" + String(p_name) + "' because it does not exist."); +void Theme::clear_color(const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!color_map.has(p_theme_type), "Cannot clear the color '" + String(p_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); + ERR_FAIL_COND_MSG(!color_map[p_theme_type].has(p_name), "Cannot clear the color '" + String(p_name) + "' because it does not exist."); - color_map[p_node_type].erase(p_name); - notify_property_list_changed(); - emit_changed(); + color_map[p_theme_type].erase(p_name); + + _emit_theme_changed(); } -void Theme::get_color_list(StringName p_node_type, List<StringName> *p_list) const { +void Theme::get_color_list(StringName p_theme_type, List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); - if (!color_map.has(p_node_type)) { + if (!color_map.has(p_theme_type)) { return; } const StringName *key = nullptr; - while ((key = color_map[p_node_type].next(key))) { + while ((key = color_map[p_theme_type].next(key))) { p_list->push_back(*key); } } -void Theme::add_color_type(const StringName &p_node_type) { - color_map[p_node_type] = HashMap<StringName, Color>(); +void Theme::add_color_type(const StringName &p_theme_type) { + if (color_map.has(p_theme_type)) { + return; + } + color_map[p_theme_type] = HashMap<StringName, Color>(); } void Theme::get_color_type_list(List<StringName> *p_list) const { @@ -874,65 +927,67 @@ void Theme::get_color_type_list(List<StringName> *p_list) const { } } -void Theme::set_constant(const StringName &p_name, const StringName &p_node_type, int p_constant) { - bool new_value = !constant_map.has(p_node_type) || !constant_map[p_node_type].has(p_name); - constant_map[p_node_type][p_name] = p_constant; +void Theme::set_constant(const StringName &p_name, const StringName &p_theme_type, int p_constant) { + constant_map[p_theme_type][p_name] = p_constant; - if (new_value) { - notify_property_list_changed(); - emit_changed(); - } + _emit_theme_changed(); } -int Theme::get_constant(const StringName &p_name, const StringName &p_node_type) const { - if (constant_map.has(p_node_type) && constant_map[p_node_type].has(p_name)) { - return constant_map[p_node_type][p_name]; +int Theme::get_constant(const StringName &p_name, const StringName &p_theme_type) const { + if (constant_map.has(p_theme_type) && constant_map[p_theme_type].has(p_name)) { + return constant_map[p_theme_type][p_name]; } else { return 0; } } -bool Theme::has_constant(const StringName &p_name, const StringName &p_node_type) const { - return (constant_map.has(p_node_type) && constant_map[p_node_type].has(p_name)); +bool Theme::has_constant(const StringName &p_name, const StringName &p_theme_type) const { + return (constant_map.has(p_theme_type) && constant_map[p_theme_type].has(p_name)); } -void Theme::rename_constant(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type) { - ERR_FAIL_COND_MSG(!constant_map.has(p_node_type), "Cannot rename the constant '" + String(p_old_name) + "' because the node type '" + String(p_node_type) + "' does not exist."); - ERR_FAIL_COND_MSG(constant_map[p_node_type].has(p_name), "Cannot rename the constant '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); - ERR_FAIL_COND_MSG(!constant_map[p_node_type].has(p_old_name), "Cannot rename the constant '" + String(p_old_name) + "' because it does not exist."); +bool Theme::has_constant_nocheck(const StringName &p_name, const StringName &p_theme_type) const { + return (constant_map.has(p_theme_type) && constant_map[p_theme_type].has(p_name)); +} - constant_map[p_node_type][p_name] = constant_map[p_node_type][p_old_name]; - constant_map[p_node_type].erase(p_old_name); +void Theme::rename_constant(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!constant_map.has(p_theme_type), "Cannot rename the constant '" + String(p_old_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); + ERR_FAIL_COND_MSG(constant_map[p_theme_type].has(p_name), "Cannot rename the constant '" + String(p_old_name) + "' because the new name '" + String(p_name) + "' already exists."); + ERR_FAIL_COND_MSG(!constant_map[p_theme_type].has(p_old_name), "Cannot rename the constant '" + String(p_old_name) + "' because it does not exist."); - notify_property_list_changed(); - emit_changed(); + constant_map[p_theme_type][p_name] = constant_map[p_theme_type][p_old_name]; + constant_map[p_theme_type].erase(p_old_name); + + _emit_theme_changed(); } -void Theme::clear_constant(const StringName &p_name, const StringName &p_node_type) { - ERR_FAIL_COND_MSG(!constant_map.has(p_node_type), "Cannot clear the constant '" + String(p_name) + "' because the node type '" + String(p_node_type) + "' does not exist."); - ERR_FAIL_COND_MSG(!constant_map[p_node_type].has(p_name), "Cannot clear the constant '" + String(p_name) + "' because it does not exist."); +void Theme::clear_constant(const StringName &p_name, const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!constant_map.has(p_theme_type), "Cannot clear the constant '" + String(p_name) + "' because the node type '" + String(p_theme_type) + "' does not exist."); + ERR_FAIL_COND_MSG(!constant_map[p_theme_type].has(p_name), "Cannot clear the constant '" + String(p_name) + "' because it does not exist."); - constant_map[p_node_type].erase(p_name); - notify_property_list_changed(); - emit_changed(); + constant_map[p_theme_type].erase(p_name); + + _emit_theme_changed(); } -void Theme::get_constant_list(StringName p_node_type, List<StringName> *p_list) const { +void Theme::get_constant_list(StringName p_theme_type, List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); - if (!constant_map.has(p_node_type)) { + if (!constant_map.has(p_theme_type)) { return; } const StringName *key = nullptr; - while ((key = constant_map[p_node_type].next(key))) { + while ((key = constant_map[p_theme_type].next(key))) { p_list->push_back(*key); } } -void Theme::add_constant_type(const StringName &p_node_type) { - constant_map[p_node_type] = HashMap<StringName, int>(); +void Theme::add_constant_type(const StringName &p_theme_type) { + if (constant_map.has(p_theme_type)) { + return; + } + constant_map[p_theme_type] = HashMap<StringName, int>(); } void Theme::get_constant_type_list(List<StringName> *p_list) const { @@ -944,63 +999,63 @@ void Theme::get_constant_type_list(List<StringName> *p_list) const { } } -void Theme::set_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_node_type, const Variant &p_value) { +void Theme::set_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_theme_type, const Variant &p_value) { switch (p_data_type) { case DATA_TYPE_COLOR: { ERR_FAIL_COND_MSG(p_value.get_type() != Variant::COLOR, "Theme item's data type (Color) does not match Variant's type (" + Variant::get_type_name(p_value.get_type()) + ")."); Color color_value = p_value; - set_color(p_name, p_node_type, color_value); + set_color(p_name, p_theme_type, color_value); } break; case DATA_TYPE_CONSTANT: { ERR_FAIL_COND_MSG(p_value.get_type() != Variant::INT, "Theme item's data type (int) does not match Variant's type (" + Variant::get_type_name(p_value.get_type()) + ")."); int constant_value = p_value; - set_constant(p_name, p_node_type, constant_value); + set_constant(p_name, p_theme_type, constant_value); } break; case DATA_TYPE_FONT: { ERR_FAIL_COND_MSG(p_value.get_type() != Variant::OBJECT, "Theme item's data type (Object) does not match Variant's type (" + Variant::get_type_name(p_value.get_type()) + ")."); Ref<Font> font_value = Object::cast_to<Font>(p_value.get_validated_object()); - set_font(p_name, p_node_type, font_value); + set_font(p_name, p_theme_type, font_value); } break; case DATA_TYPE_FONT_SIZE: { ERR_FAIL_COND_MSG(p_value.get_type() != Variant::INT, "Theme item's data type (int) does not match Variant's type (" + Variant::get_type_name(p_value.get_type()) + ")."); int font_size_value = p_value; - set_font_size(p_name, p_node_type, font_size_value); + set_font_size(p_name, p_theme_type, font_size_value); } break; case DATA_TYPE_ICON: { ERR_FAIL_COND_MSG(p_value.get_type() != Variant::OBJECT, "Theme item's data type (Object) does not match Variant's type (" + Variant::get_type_name(p_value.get_type()) + ")."); Ref<Texture2D> icon_value = Object::cast_to<Texture2D>(p_value.get_validated_object()); - set_icon(p_name, p_node_type, icon_value); + set_icon(p_name, p_theme_type, icon_value); } break; case DATA_TYPE_STYLEBOX: { ERR_FAIL_COND_MSG(p_value.get_type() != Variant::OBJECT, "Theme item's data type (Object) does not match Variant's type (" + Variant::get_type_name(p_value.get_type()) + ")."); Ref<StyleBox> stylebox_value = Object::cast_to<StyleBox>(p_value.get_validated_object()); - set_stylebox(p_name, p_node_type, stylebox_value); + set_stylebox(p_name, p_theme_type, stylebox_value); } break; case DATA_TYPE_MAX: break; // Can't happen, but silences warning. } } -Variant Theme::get_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_node_type) const { +Variant Theme::get_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_theme_type) const { switch (p_data_type) { case DATA_TYPE_COLOR: - return get_color(p_name, p_node_type); + return get_color(p_name, p_theme_type); case DATA_TYPE_CONSTANT: - return get_constant(p_name, p_node_type); + return get_constant(p_name, p_theme_type); case DATA_TYPE_FONT: - return get_font(p_name, p_node_type); + return get_font(p_name, p_theme_type); case DATA_TYPE_FONT_SIZE: - return get_font_size(p_name, p_node_type); + return get_font_size(p_name, p_theme_type); case DATA_TYPE_ICON: - return get_icon(p_name, p_node_type); + return get_icon(p_name, p_theme_type); case DATA_TYPE_STYLEBOX: - return get_stylebox(p_name, p_node_type); + return get_stylebox(p_name, p_theme_type); case DATA_TYPE_MAX: break; // Can't happen, but silences warning. } @@ -1008,20 +1063,41 @@ Variant Theme::get_theme_item(DataType p_data_type, const StringName &p_name, co return Variant(); } -bool Theme::has_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_node_type) const { +bool Theme::has_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_theme_type) const { + switch (p_data_type) { + case DATA_TYPE_COLOR: + return has_color(p_name, p_theme_type); + case DATA_TYPE_CONSTANT: + return has_constant(p_name, p_theme_type); + case DATA_TYPE_FONT: + return has_font(p_name, p_theme_type); + case DATA_TYPE_FONT_SIZE: + return has_font_size(p_name, p_theme_type); + case DATA_TYPE_ICON: + return has_icon(p_name, p_theme_type); + case DATA_TYPE_STYLEBOX: + return has_stylebox(p_name, p_theme_type); + case DATA_TYPE_MAX: + break; // Can't happen, but silences warning. + } + + return false; +} + +bool Theme::has_theme_item_nocheck(DataType p_data_type, const StringName &p_name, const StringName &p_theme_type) const { switch (p_data_type) { case DATA_TYPE_COLOR: - return has_color(p_name, p_node_type); + return has_color_nocheck(p_name, p_theme_type); case DATA_TYPE_CONSTANT: - return has_constant(p_name, p_node_type); + return has_constant_nocheck(p_name, p_theme_type); case DATA_TYPE_FONT: - return has_font(p_name, p_node_type); + return has_font_nocheck(p_name, p_theme_type); case DATA_TYPE_FONT_SIZE: - return has_font_size(p_name, p_node_type); + return has_font_size_nocheck(p_name, p_theme_type); case DATA_TYPE_ICON: - return has_icon(p_name, p_node_type); + return has_icon_nocheck(p_name, p_theme_type); case DATA_TYPE_STYLEBOX: - return has_stylebox(p_name, p_node_type); + return has_stylebox_nocheck(p_name, p_theme_type); case DATA_TYPE_MAX: break; // Can't happen, but silences warning. } @@ -1029,100 +1105,100 @@ bool Theme::has_theme_item(DataType p_data_type, const StringName &p_name, const return false; } -void Theme::rename_theme_item(DataType p_data_type, const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type) { +void Theme::rename_theme_item(DataType p_data_type, const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type) { switch (p_data_type) { case DATA_TYPE_COLOR: - rename_color(p_old_name, p_name, p_node_type); + rename_color(p_old_name, p_name, p_theme_type); break; case DATA_TYPE_CONSTANT: - rename_constant(p_old_name, p_name, p_node_type); + rename_constant(p_old_name, p_name, p_theme_type); break; case DATA_TYPE_FONT: - rename_font(p_old_name, p_name, p_node_type); + rename_font(p_old_name, p_name, p_theme_type); break; case DATA_TYPE_FONT_SIZE: - rename_font_size(p_old_name, p_name, p_node_type); + rename_font_size(p_old_name, p_name, p_theme_type); break; case DATA_TYPE_ICON: - rename_icon(p_old_name, p_name, p_node_type); + rename_icon(p_old_name, p_name, p_theme_type); break; case DATA_TYPE_STYLEBOX: - rename_stylebox(p_old_name, p_name, p_node_type); + rename_stylebox(p_old_name, p_name, p_theme_type); break; case DATA_TYPE_MAX: break; // Can't happen, but silences warning. } } -void Theme::clear_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_node_type) { +void Theme::clear_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_theme_type) { switch (p_data_type) { case DATA_TYPE_COLOR: - clear_color(p_name, p_node_type); + clear_color(p_name, p_theme_type); break; case DATA_TYPE_CONSTANT: - clear_constant(p_name, p_node_type); + clear_constant(p_name, p_theme_type); break; case DATA_TYPE_FONT: - clear_font(p_name, p_node_type); + clear_font(p_name, p_theme_type); break; case DATA_TYPE_FONT_SIZE: - clear_font_size(p_name, p_node_type); + clear_font_size(p_name, p_theme_type); break; case DATA_TYPE_ICON: - clear_icon(p_name, p_node_type); + clear_icon(p_name, p_theme_type); break; case DATA_TYPE_STYLEBOX: - clear_stylebox(p_name, p_node_type); + clear_stylebox(p_name, p_theme_type); break; case DATA_TYPE_MAX: break; // Can't happen, but silences warning. } } -void Theme::get_theme_item_list(DataType p_data_type, StringName p_node_type, List<StringName> *p_list) const { +void Theme::get_theme_item_list(DataType p_data_type, StringName p_theme_type, List<StringName> *p_list) const { switch (p_data_type) { case DATA_TYPE_COLOR: - get_color_list(p_node_type, p_list); + get_color_list(p_theme_type, p_list); break; case DATA_TYPE_CONSTANT: - get_constant_list(p_node_type, p_list); + get_constant_list(p_theme_type, p_list); break; case DATA_TYPE_FONT: - get_font_list(p_node_type, p_list); + get_font_list(p_theme_type, p_list); break; case DATA_TYPE_FONT_SIZE: - get_font_size_list(p_node_type, p_list); + get_font_size_list(p_theme_type, p_list); break; case DATA_TYPE_ICON: - get_icon_list(p_node_type, p_list); + get_icon_list(p_theme_type, p_list); break; case DATA_TYPE_STYLEBOX: - get_stylebox_list(p_node_type, p_list); + get_stylebox_list(p_theme_type, p_list); break; case DATA_TYPE_MAX: break; // Can't happen, but silences warning. } } -void Theme::add_theme_item_type(DataType p_data_type, const StringName &p_node_type) { +void Theme::add_theme_item_type(DataType p_data_type, const StringName &p_theme_type) { switch (p_data_type) { case DATA_TYPE_COLOR: - add_color_type(p_node_type); + add_color_type(p_theme_type); break; case DATA_TYPE_CONSTANT: - add_constant_type(p_node_type); + add_constant_type(p_theme_type); break; case DATA_TYPE_FONT: - add_font_type(p_node_type); + add_font_type(p_theme_type); break; case DATA_TYPE_FONT_SIZE: - add_font_size_type(p_node_type); + add_font_size_type(p_theme_type); break; case DATA_TYPE_ICON: - add_icon_type(p_node_type); + add_icon_type(p_theme_type); break; case DATA_TYPE_STYLEBOX: - add_stylebox_type(p_node_type); + add_stylebox_type(p_theme_type); break; case DATA_TYPE_MAX: break; // Can't happen, but silences warning. @@ -1154,8 +1230,74 @@ void Theme::get_theme_item_type_list(DataType p_data_type, List<StringName> *p_l } } +void Theme::set_type_variation(const StringName &p_theme_type, const StringName &p_base_type) { + ERR_FAIL_COND_MSG(p_theme_type == StringName(), "An empty theme type cannot be marked as a variation of another type."); + ERR_FAIL_COND_MSG(ClassDB::class_exists(p_theme_type), "A type associated with a built-in class cannot be marked as a variation of another type."); + ERR_FAIL_COND_MSG(p_base_type == StringName(), "An empty theme type cannot be the base type of a variation. Use clear_type_variation() instead if you want to unmark '" + String(p_theme_type) + "' as a variation."); + + if (variation_map.has(p_theme_type)) { + StringName old_base = variation_map[p_theme_type]; + variation_base_map[old_base].erase(p_theme_type); + } + + variation_map[p_theme_type] = p_base_type; + variation_base_map[p_base_type].push_back(p_theme_type); + + _emit_theme_changed(); +} + +bool Theme::is_type_variation(const StringName &p_theme_type, const StringName &p_base_type) const { + return (variation_map.has(p_theme_type) && variation_map[p_theme_type] == p_base_type); +} + +void Theme::clear_type_variation(const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!variation_map.has(p_theme_type), "Cannot clear the type variation '" + String(p_theme_type) + "' because it does not exist."); + + StringName base_type = variation_map[p_theme_type]; + variation_base_map[base_type].erase(p_theme_type); + variation_map.erase(p_theme_type); + + _emit_theme_changed(); +} + +StringName Theme::get_type_variation_base(const StringName &p_theme_type) const { + if (!variation_map.has(p_theme_type)) { + return StringName(); + } + + return variation_map[p_theme_type]; +} + +void Theme::get_type_variation_list(const StringName &p_base_type, List<StringName> *p_list) const { + ERR_FAIL_NULL(p_list); + + if (!variation_base_map.has(p_base_type)) { + return; + } + + for (const List<StringName>::Element *E = variation_base_map[p_base_type].front(); E; E = E->next()) { + // Prevent infinite loops if variants were set to be cross-dependent (that's still invalid usage, but handling for stability sake). + if (p_list->find(E->get())) { + continue; + } + + p_list->push_back(E->get()); + // Continue looking for sub-variations. + get_type_variation_list(E->get(), p_list); + } +} + +void Theme::_freeze_change_propagation() { + no_change_propagation = true; +} + +void Theme::_unfreeze_and_propagate_changes() { + no_change_propagation = false; + _emit_theme_changed(); +} + void Theme::clear() { - //these need disconnecting + // These items need disconnecting. { const StringName *K = nullptr; while ((K = icon_map.next(K))) { @@ -1198,11 +1340,14 @@ void Theme::clear() { icon_map.clear(); style_map.clear(); font_map.clear(); + font_size_map.clear(); color_map.clear(); constant_map.clear(); - notify_property_list_changed(); - emit_changed(); + variation_map.clear(); + variation_base_map.clear(); + + _emit_theme_changed(); } void Theme::copy_default_theme() { @@ -1216,6 +1361,8 @@ void Theme::copy_theme(const Ref<Theme> &p_other) { return; } + _freeze_change_propagation(); + // These items need reconnecting, so add them normally. { const StringName *K = nullptr; @@ -1252,8 +1399,10 @@ void Theme::copy_theme(const Ref<Theme> &p_other) { color_map = p_other->color_map; constant_map = p_other->constant_map; - notify_property_list_changed(); - emit_changed(); + variation_map = p_other->variation_map; + variation_base_map = p_other->variation_base_map; + + _unfreeze_and_propagate_changes(); } void Theme::get_type_list(List<StringName> *p_list) const { @@ -1262,30 +1411,42 @@ void Theme::get_type_list(List<StringName> *p_list) const { Set<StringName> types; const StringName *key = nullptr; + // Icons. while ((key = icon_map.next(key))) { types.insert(*key); } key = nullptr; + // StyleBoxes. while ((key = style_map.next(key))) { types.insert(*key); } key = nullptr; + // Fonts. while ((key = font_map.next(key))) { types.insert(*key); } key = nullptr; + // Font sizes. + while ((key = font_size_map.next(key))) { + types.insert(*key); + } + + key = nullptr; + + // Colors. while ((key = color_map.next(key))) { types.insert(*key); } key = nullptr; + // Constants. while ((key = constant_map.next(key))) { types.insert(*key); } @@ -1295,56 +1456,82 @@ void Theme::get_type_list(List<StringName> *p_list) const { } } +void Theme::get_type_dependencies(const StringName &p_base_type, const StringName &p_type_variation, List<StringName> *p_list) { + ERR_FAIL_NULL(p_list); + + // Build the dependency chain for type variations. + if (p_type_variation != StringName()) { + StringName variation_name = p_type_variation; + while (variation_name != StringName()) { + p_list->push_back(variation_name); + variation_name = get_type_variation_base(variation_name); + + // If we have reached the base type dependency, it's safe to stop (assuming no funny business was done to the Theme). + if (variation_name == p_base_type) { + break; + } + } + } + + // Continue building the chain using native class hierarchy. + StringName class_name = p_base_type; + while (class_name != StringName()) { + p_list->push_back(class_name); + class_name = ClassDB::get_parent_class_nocheck(class_name); + } +} + void Theme::reset_state() { clear(); } + void Theme::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_icon", "name", "node_type", "texture"), &Theme::set_icon); - ClassDB::bind_method(D_METHOD("get_icon", "name", "node_type"), &Theme::get_icon); - ClassDB::bind_method(D_METHOD("has_icon", "name", "node_type"), &Theme::has_icon); - ClassDB::bind_method(D_METHOD("rename_icon", "old_name", "name", "node_type"), &Theme::rename_icon); - ClassDB::bind_method(D_METHOD("clear_icon", "name", "node_type"), &Theme::clear_icon); - ClassDB::bind_method(D_METHOD("get_icon_list", "node_type"), &Theme::_get_icon_list); + ClassDB::bind_method(D_METHOD("set_icon", "name", "theme_type", "texture"), &Theme::set_icon); + ClassDB::bind_method(D_METHOD("get_icon", "name", "theme_type"), &Theme::get_icon); + ClassDB::bind_method(D_METHOD("has_icon", "name", "theme_type"), &Theme::has_icon); + ClassDB::bind_method(D_METHOD("rename_icon", "old_name", "name", "theme_type"), &Theme::rename_icon); + ClassDB::bind_method(D_METHOD("clear_icon", "name", "theme_type"), &Theme::clear_icon); + ClassDB::bind_method(D_METHOD("get_icon_list", "theme_type"), &Theme::_get_icon_list); ClassDB::bind_method(D_METHOD("get_icon_type_list"), &Theme::_get_icon_type_list); - ClassDB::bind_method(D_METHOD("set_stylebox", "name", "node_type", "texture"), &Theme::set_stylebox); - ClassDB::bind_method(D_METHOD("get_stylebox", "name", "node_type"), &Theme::get_stylebox); - ClassDB::bind_method(D_METHOD("has_stylebox", "name", "node_type"), &Theme::has_stylebox); - ClassDB::bind_method(D_METHOD("rename_stylebox", "old_name", "name", "node_type"), &Theme::rename_stylebox); - ClassDB::bind_method(D_METHOD("clear_stylebox", "name", "node_type"), &Theme::clear_stylebox); - ClassDB::bind_method(D_METHOD("get_stylebox_list", "node_type"), &Theme::_get_stylebox_list); + ClassDB::bind_method(D_METHOD("set_stylebox", "name", "theme_type", "texture"), &Theme::set_stylebox); + ClassDB::bind_method(D_METHOD("get_stylebox", "name", "theme_type"), &Theme::get_stylebox); + ClassDB::bind_method(D_METHOD("has_stylebox", "name", "theme_type"), &Theme::has_stylebox); + ClassDB::bind_method(D_METHOD("rename_stylebox", "old_name", "name", "theme_type"), &Theme::rename_stylebox); + ClassDB::bind_method(D_METHOD("clear_stylebox", "name", "theme_type"), &Theme::clear_stylebox); + ClassDB::bind_method(D_METHOD("get_stylebox_list", "theme_type"), &Theme::_get_stylebox_list); ClassDB::bind_method(D_METHOD("get_stylebox_type_list"), &Theme::_get_stylebox_type_list); - ClassDB::bind_method(D_METHOD("set_font", "name", "node_type", "font"), &Theme::set_font); - ClassDB::bind_method(D_METHOD("get_font", "name", "node_type"), &Theme::get_font); - ClassDB::bind_method(D_METHOD("has_font", "name", "node_type"), &Theme::has_font); - ClassDB::bind_method(D_METHOD("rename_font", "old_name", "name", "node_type"), &Theme::rename_font); - ClassDB::bind_method(D_METHOD("clear_font", "name", "node_type"), &Theme::clear_font); - ClassDB::bind_method(D_METHOD("get_font_list", "node_type"), &Theme::_get_font_list); + ClassDB::bind_method(D_METHOD("set_font", "name", "theme_type", "font"), &Theme::set_font); + ClassDB::bind_method(D_METHOD("get_font", "name", "theme_type"), &Theme::get_font); + ClassDB::bind_method(D_METHOD("has_font", "name", "theme_type"), &Theme::has_font); + ClassDB::bind_method(D_METHOD("rename_font", "old_name", "name", "theme_type"), &Theme::rename_font); + ClassDB::bind_method(D_METHOD("clear_font", "name", "theme_type"), &Theme::clear_font); + ClassDB::bind_method(D_METHOD("get_font_list", "theme_type"), &Theme::_get_font_list); ClassDB::bind_method(D_METHOD("get_font_type_list"), &Theme::_get_font_type_list); - ClassDB::bind_method(D_METHOD("set_font_size", "name", "node_type", "font_size"), &Theme::set_font_size); - ClassDB::bind_method(D_METHOD("get_font_size", "name", "node_type"), &Theme::get_font_size); - ClassDB::bind_method(D_METHOD("has_font_size", "name", "node_type"), &Theme::has_font_size); - ClassDB::bind_method(D_METHOD("rename_font_size", "old_name", "name", "node_type"), &Theme::rename_font_size); - ClassDB::bind_method(D_METHOD("clear_font_size", "name", "node_type"), &Theme::clear_font_size); - ClassDB::bind_method(D_METHOD("get_font_size_list", "node_type"), &Theme::_get_font_size_list); + ClassDB::bind_method(D_METHOD("set_font_size", "name", "theme_type", "font_size"), &Theme::set_font_size); + ClassDB::bind_method(D_METHOD("get_font_size", "name", "theme_type"), &Theme::get_font_size); + ClassDB::bind_method(D_METHOD("has_font_size", "name", "theme_type"), &Theme::has_font_size); + ClassDB::bind_method(D_METHOD("rename_font_size", "old_name", "name", "theme_type"), &Theme::rename_font_size); + ClassDB::bind_method(D_METHOD("clear_font_size", "name", "theme_type"), &Theme::clear_font_size); + ClassDB::bind_method(D_METHOD("get_font_size_list", "theme_type"), &Theme::_get_font_size_list); ClassDB::bind_method(D_METHOD("get_font_size_type_list"), &Theme::_get_font_size_type_list); - ClassDB::bind_method(D_METHOD("set_color", "name", "node_type", "color"), &Theme::set_color); - ClassDB::bind_method(D_METHOD("get_color", "name", "node_type"), &Theme::get_color); - ClassDB::bind_method(D_METHOD("has_color", "name", "node_type"), &Theme::has_color); - ClassDB::bind_method(D_METHOD("rename_color", "old_name", "name", "node_type"), &Theme::rename_color); - ClassDB::bind_method(D_METHOD("clear_color", "name", "node_type"), &Theme::clear_color); - ClassDB::bind_method(D_METHOD("get_color_list", "node_type"), &Theme::_get_color_list); + ClassDB::bind_method(D_METHOD("set_color", "name", "theme_type", "color"), &Theme::set_color); + ClassDB::bind_method(D_METHOD("get_color", "name", "theme_type"), &Theme::get_color); + ClassDB::bind_method(D_METHOD("has_color", "name", "theme_type"), &Theme::has_color); + ClassDB::bind_method(D_METHOD("rename_color", "old_name", "name", "theme_type"), &Theme::rename_color); + ClassDB::bind_method(D_METHOD("clear_color", "name", "theme_type"), &Theme::clear_color); + ClassDB::bind_method(D_METHOD("get_color_list", "theme_type"), &Theme::_get_color_list); ClassDB::bind_method(D_METHOD("get_color_type_list"), &Theme::_get_color_type_list); - ClassDB::bind_method(D_METHOD("set_constant", "name", "node_type", "constant"), &Theme::set_constant); - ClassDB::bind_method(D_METHOD("get_constant", "name", "node_type"), &Theme::get_constant); - ClassDB::bind_method(D_METHOD("has_constant", "name", "node_type"), &Theme::has_constant); - ClassDB::bind_method(D_METHOD("rename_constant", "old_name", "name", "node_type"), &Theme::rename_constant); - ClassDB::bind_method(D_METHOD("clear_constant", "name", "node_type"), &Theme::clear_constant); - ClassDB::bind_method(D_METHOD("get_constant_list", "node_type"), &Theme::_get_constant_list); + ClassDB::bind_method(D_METHOD("set_constant", "name", "theme_type", "constant"), &Theme::set_constant); + ClassDB::bind_method(D_METHOD("get_constant", "name", "theme_type"), &Theme::get_constant); + ClassDB::bind_method(D_METHOD("has_constant", "name", "theme_type"), &Theme::has_constant); + ClassDB::bind_method(D_METHOD("rename_constant", "old_name", "name", "theme_type"), &Theme::rename_constant); + ClassDB::bind_method(D_METHOD("clear_constant", "name", "theme_type"), &Theme::clear_constant); + ClassDB::bind_method(D_METHOD("get_constant_list", "theme_type"), &Theme::_get_constant_list); ClassDB::bind_method(D_METHOD("get_constant_type_list"), &Theme::_get_constant_type_list); ClassDB::bind_method(D_METHOD("clear"), &Theme::clear); @@ -1355,14 +1542,20 @@ void Theme::_bind_methods() { ClassDB::bind_method(D_METHOD("set_default_font_size", "font_size"), &Theme::set_default_theme_font_size); ClassDB::bind_method(D_METHOD("get_default_font_size"), &Theme::get_default_theme_font_size); - ClassDB::bind_method(D_METHOD("set_theme_item", "data_type", "name", "node_type", "value"), &Theme::set_theme_item); - ClassDB::bind_method(D_METHOD("get_theme_item", "data_type", "name", "node_type"), &Theme::get_theme_item); - ClassDB::bind_method(D_METHOD("has_theme_item", "data_type", "name", "node_type"), &Theme::has_theme_item); - ClassDB::bind_method(D_METHOD("rename_theme_item", "data_type", "old_name", "name", "node_type"), &Theme::rename_theme_item); - ClassDB::bind_method(D_METHOD("clear_theme_item", "data_type", "name", "node_type"), &Theme::clear_theme_item); - ClassDB::bind_method(D_METHOD("get_theme_item_list", "data_type", "node_type"), &Theme::_get_theme_item_list); + ClassDB::bind_method(D_METHOD("set_theme_item", "data_type", "name", "theme_type", "value"), &Theme::set_theme_item); + ClassDB::bind_method(D_METHOD("get_theme_item", "data_type", "name", "theme_type"), &Theme::get_theme_item); + ClassDB::bind_method(D_METHOD("has_theme_item", "data_type", "name", "theme_type"), &Theme::has_theme_item); + ClassDB::bind_method(D_METHOD("rename_theme_item", "data_type", "old_name", "name", "theme_type"), &Theme::rename_theme_item); + ClassDB::bind_method(D_METHOD("clear_theme_item", "data_type", "name", "theme_type"), &Theme::clear_theme_item); + ClassDB::bind_method(D_METHOD("get_theme_item_list", "data_type", "theme_type"), &Theme::_get_theme_item_list); ClassDB::bind_method(D_METHOD("get_theme_item_type_list", "data_type"), &Theme::_get_theme_item_type_list); + ClassDB::bind_method(D_METHOD("set_type_variation", "theme_type", "base_type"), &Theme::set_type_variation); + ClassDB::bind_method(D_METHOD("is_type_variation", "theme_type", "base_type"), &Theme::is_type_variation); + ClassDB::bind_method(D_METHOD("clear_type_variation", "theme_type"), &Theme::clear_type_variation); + ClassDB::bind_method(D_METHOD("get_type_variation_base", "theme_type"), &Theme::get_type_variation_base); + ClassDB::bind_method(D_METHOD("get_type_variation_list", "base_type"), &Theme::_get_type_variation_list); + ClassDB::bind_method(D_METHOD("get_type_list"), &Theme::_get_type_list); ClassDB::bind_method("copy_default_theme", &Theme::copy_default_theme); @@ -1371,12 +1564,12 @@ void Theme::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "default_font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_default_font", "get_default_font"); ADD_PROPERTY(PropertyInfo(Variant::INT, "default_font_size"), "set_default_font_size", "get_default_font_size"); - BIND_ENUM_CONSTANT(DATA_TYPE_ICON); - BIND_ENUM_CONSTANT(DATA_TYPE_STYLEBOX); - BIND_ENUM_CONSTANT(DATA_TYPE_FONT); - BIND_ENUM_CONSTANT(DATA_TYPE_FONT_SIZE); BIND_ENUM_CONSTANT(DATA_TYPE_COLOR); BIND_ENUM_CONSTANT(DATA_TYPE_CONSTANT); + BIND_ENUM_CONSTANT(DATA_TYPE_FONT); + BIND_ENUM_CONSTANT(DATA_TYPE_FONT_SIZE); + BIND_ENUM_CONSTANT(DATA_TYPE_ICON); + BIND_ENUM_CONSTANT(DATA_TYPE_STYLEBOX); BIND_ENUM_CONSTANT(DATA_TYPE_MAX); } diff --git a/scene/resources/theme.h b/scene/resources/theme.h index eb918fac69..8a8fc28be1 100644 --- a/scene/resources/theme.h +++ b/scene/resources/theme.h @@ -41,6 +41,12 @@ class Theme : public Resource { GDCLASS(Theme, Resource); RES_BASE_EXTENSION("theme"); +#ifdef TOOLS_ENABLED + friend class ThemeItemImportTree; + friend class ThemeItemEditorDialog; + friend class ThemeTypeEditor; +#endif + public: enum DataType { DATA_TYPE_COLOR, @@ -53,6 +59,8 @@ public: }; private: + bool no_change_propagation = false; + void _emit_theme_changed(); HashMap<StringName, HashMap<StringName, Ref<Texture2D>>> icon_map; @@ -61,22 +69,26 @@ private: HashMap<StringName, HashMap<StringName, int>> font_size_map; HashMap<StringName, HashMap<StringName, Color>> color_map; HashMap<StringName, HashMap<StringName, int>> constant_map; + HashMap<StringName, StringName> variation_map; + HashMap<StringName, List<StringName>> variation_base_map; - Vector<String> _get_icon_list(const String &p_node_type) const; + Vector<String> _get_icon_list(const String &p_theme_type) const; Vector<String> _get_icon_type_list() const; - Vector<String> _get_stylebox_list(const String &p_node_type) const; + Vector<String> _get_stylebox_list(const String &p_theme_type) const; Vector<String> _get_stylebox_type_list() const; - Vector<String> _get_font_list(const String &p_node_type) const; + Vector<String> _get_font_list(const String &p_theme_type) const; Vector<String> _get_font_type_list() const; - Vector<String> _get_font_size_list(const String &p_node_type) const; + Vector<String> _get_font_size_list(const String &p_theme_type) const; Vector<String> _get_font_size_type_list() const; - Vector<String> _get_color_list(const String &p_node_type) const; + Vector<String> _get_color_list(const String &p_theme_type) const; Vector<String> _get_color_type_list() const; - Vector<String> _get_constant_list(const String &p_node_type) const; + Vector<String> _get_constant_list(const String &p_theme_type) const; Vector<String> _get_constant_type_list() const; - Vector<String> _get_theme_item_list(DataType p_data_type, const String &p_node_type) const; + Vector<String> _get_theme_item_list(DataType p_data_type, const String &p_theme_type) const; Vector<String> _get_theme_item_type_list(DataType p_data_type) const; + + Vector<String> _get_type_variation_list(const StringName &p_theme_type) const; Vector<String> _get_type_list() const; protected: @@ -96,6 +108,9 @@ protected: static void _bind_methods(); + void _freeze_change_propagation(); + void _unfreeze_and_propagate_changes(); + virtual void reset_state() override; public: @@ -116,70 +131,84 @@ public: void set_default_theme_font_size(int p_font_size); int get_default_theme_font_size() const; - void set_icon(const StringName &p_name, const StringName &p_node_type, const Ref<Texture2D> &p_icon); - Ref<Texture2D> get_icon(const StringName &p_name, const StringName &p_node_type) const; - bool has_icon(const StringName &p_name, const StringName &p_node_type) const; - void rename_icon(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type); - void clear_icon(const StringName &p_name, const StringName &p_node_type); - void get_icon_list(StringName p_node_type, List<StringName> *p_list) const; - void add_icon_type(const StringName &p_node_type); + void set_icon(const StringName &p_name, const StringName &p_theme_type, const Ref<Texture2D> &p_icon); + Ref<Texture2D> get_icon(const StringName &p_name, const StringName &p_theme_type) const; + bool has_icon(const StringName &p_name, const StringName &p_theme_type) const; + bool has_icon_nocheck(const StringName &p_name, const StringName &p_theme_type) const; + void rename_icon(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type); + void clear_icon(const StringName &p_name, const StringName &p_theme_type); + void get_icon_list(StringName p_theme_type, List<StringName> *p_list) const; + void add_icon_type(const StringName &p_theme_type); void get_icon_type_list(List<StringName> *p_list) const; - void set_stylebox(const StringName &p_name, const StringName &p_node_type, const Ref<StyleBox> &p_style); - Ref<StyleBox> get_stylebox(const StringName &p_name, const StringName &p_node_type) const; - bool has_stylebox(const StringName &p_name, const StringName &p_node_type) const; - void rename_stylebox(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type); - void clear_stylebox(const StringName &p_name, const StringName &p_node_type); - void get_stylebox_list(StringName p_node_type, List<StringName> *p_list) const; - void add_stylebox_type(const StringName &p_node_type); + void set_stylebox(const StringName &p_name, const StringName &p_theme_type, const Ref<StyleBox> &p_style); + Ref<StyleBox> get_stylebox(const StringName &p_name, const StringName &p_theme_type) const; + bool has_stylebox(const StringName &p_name, const StringName &p_theme_type) const; + bool has_stylebox_nocheck(const StringName &p_name, const StringName &p_theme_type) const; + void rename_stylebox(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type); + void clear_stylebox(const StringName &p_name, const StringName &p_theme_type); + void get_stylebox_list(StringName p_theme_type, List<StringName> *p_list) const; + void add_stylebox_type(const StringName &p_theme_type); void get_stylebox_type_list(List<StringName> *p_list) const; - void set_font(const StringName &p_name, const StringName &p_node_type, const Ref<Font> &p_font); - Ref<Font> get_font(const StringName &p_name, const StringName &p_node_type) const; - bool has_font(const StringName &p_name, const StringName &p_node_type) const; - void rename_font(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type); - void clear_font(const StringName &p_name, const StringName &p_node_type); - void get_font_list(StringName p_node_type, List<StringName> *p_list) const; - void add_font_type(const StringName &p_node_type); + void set_font(const StringName &p_name, const StringName &p_theme_type, const Ref<Font> &p_font); + Ref<Font> get_font(const StringName &p_name, const StringName &p_theme_type) const; + bool has_font(const StringName &p_name, const StringName &p_theme_type) const; + bool has_font_nocheck(const StringName &p_name, const StringName &p_theme_type) const; + void rename_font(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type); + void clear_font(const StringName &p_name, const StringName &p_theme_type); + void get_font_list(StringName p_theme_type, List<StringName> *p_list) const; + void add_font_type(const StringName &p_theme_type); void get_font_type_list(List<StringName> *p_list) const; - void set_font_size(const StringName &p_name, const StringName &p_node_type, int p_font_size); - int get_font_size(const StringName &p_name, const StringName &p_node_type) const; - bool has_font_size(const StringName &p_name, const StringName &p_node_type) const; - void rename_font_size(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type); - void clear_font_size(const StringName &p_name, const StringName &p_node_type); - void get_font_size_list(StringName p_node_type, List<StringName> *p_list) const; - void add_font_size_type(const StringName &p_node_type); + void set_font_size(const StringName &p_name, const StringName &p_theme_type, int p_font_size); + int get_font_size(const StringName &p_name, const StringName &p_theme_type) const; + bool has_font_size(const StringName &p_name, const StringName &p_theme_type) const; + bool has_font_size_nocheck(const StringName &p_name, const StringName &p_theme_type) const; + void rename_font_size(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type); + void clear_font_size(const StringName &p_name, const StringName &p_theme_type); + void get_font_size_list(StringName p_theme_type, List<StringName> *p_list) const; + void add_font_size_type(const StringName &p_theme_type); void get_font_size_type_list(List<StringName> *p_list) const; - void set_color(const StringName &p_name, const StringName &p_node_type, const Color &p_color); - Color get_color(const StringName &p_name, const StringName &p_node_type) const; - bool has_color(const StringName &p_name, const StringName &p_node_type) const; - void rename_color(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type); - void clear_color(const StringName &p_name, const StringName &p_node_type); - void get_color_list(StringName p_node_type, List<StringName> *p_list) const; - void add_color_type(const StringName &p_node_type); + void set_color(const StringName &p_name, const StringName &p_theme_type, const Color &p_color); + Color get_color(const StringName &p_name, const StringName &p_theme_type) const; + bool has_color(const StringName &p_name, const StringName &p_theme_type) const; + bool has_color_nocheck(const StringName &p_name, const StringName &p_theme_type) const; + void rename_color(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type); + void clear_color(const StringName &p_name, const StringName &p_theme_type); + void get_color_list(StringName p_theme_type, List<StringName> *p_list) const; + void add_color_type(const StringName &p_theme_type); void get_color_type_list(List<StringName> *p_list) const; - void set_constant(const StringName &p_name, const StringName &p_node_type, int p_constant); - int get_constant(const StringName &p_name, const StringName &p_node_type) const; - bool has_constant(const StringName &p_name, const StringName &p_node_type) const; - void rename_constant(const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type); - void clear_constant(const StringName &p_name, const StringName &p_node_type); - void get_constant_list(StringName p_node_type, List<StringName> *p_list) const; - void add_constant_type(const StringName &p_node_type); + void set_constant(const StringName &p_name, const StringName &p_theme_type, int p_constant); + int get_constant(const StringName &p_name, const StringName &p_theme_type) const; + bool has_constant(const StringName &p_name, const StringName &p_theme_type) const; + bool has_constant_nocheck(const StringName &p_name, const StringName &p_theme_type) const; + void rename_constant(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type); + void clear_constant(const StringName &p_name, const StringName &p_theme_type); + void get_constant_list(StringName p_theme_type, List<StringName> *p_list) const; + void add_constant_type(const StringName &p_theme_type); void get_constant_type_list(List<StringName> *p_list) const; - void set_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_node_type, const Variant &p_value); - Variant get_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_node_type) const; - bool has_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_node_type) const; - void rename_theme_item(DataType p_data_type, const StringName &p_old_name, const StringName &p_name, const StringName &p_node_type); - void clear_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_node_type); - void get_theme_item_list(DataType p_data_type, StringName p_node_type, List<StringName> *p_list) const; - void add_theme_item_type(DataType p_data_type, const StringName &p_node_type); + void set_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_theme_type, const Variant &p_value); + Variant get_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_theme_type) const; + bool has_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_theme_type) const; + bool has_theme_item_nocheck(DataType p_data_type, const StringName &p_name, const StringName &p_theme_type) const; + void rename_theme_item(DataType p_data_type, const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type); + void clear_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_theme_type); + void get_theme_item_list(DataType p_data_type, StringName p_theme_type, List<StringName> *p_list) const; + void add_theme_item_type(DataType p_data_type, const StringName &p_theme_type); void get_theme_item_type_list(DataType p_data_type, List<StringName> *p_list) const; + void set_type_variation(const StringName &p_theme_type, const StringName &p_base_type); + bool is_type_variation(const StringName &p_theme_type, const StringName &p_base_type) const; + void clear_type_variation(const StringName &p_theme_type); + StringName get_type_variation_base(const StringName &p_theme_type) const; + void get_type_variation_list(const StringName &p_base_type, List<StringName> *p_list) const; + void get_type_list(List<StringName> *p_list) const; + void get_type_dependencies(const StringName &p_base_type, const StringName &p_type_variant, List<StringName> *p_list); void copy_default_theme(); void copy_theme(const Ref<Theme> &p_other); diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index 84be69d0d6..deee22f05f 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -30,1150 +30,4594 @@ #include "tile_set.h" -#include "core/config/engine.h" +#include "core/core_string_names.h" #include "core/math/geometry_2d.h" -#include "core/variant/array.h" +#include "core/templates/local_vector.h" + +#include "scene/2d/navigation_region_2d.h" +#include "scene/gui/control.h" +#include "scene/resources/convex_polygon_shape_2d.h" +#include "servers/navigation_server_2d.h" + +/////////////////////////////// TileSet ////////////////////////////////////// + +const char *TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[] = { + "right_side", + "right_corner", + "bottom_right_side", + "bottom_right_corner", + "bottom_side", + "bottom_corner", + "bottom_left_side", + "bottom_left_corner", + "left_side", + "left_corner", + "top_left_side", + "top_left_corner", + "top_side", + "top_corner", + "top_right_side", + "top_right_corner" +}; + +// --- Plugins --- +Vector<TileSetPlugin *> TileSet::get_tile_set_atlas_plugins() const { + return tile_set_plugins_vector; +} -bool TileSet::_set(const StringName &p_name, const Variant &p_value) { - String n = p_name; - int slash = n.find("/"); - if (slash == -1) { - return false; +// -- Shape and layout -- +void TileSet::set_tile_shape(TileSet::TileShape p_shape) { + tile_shape = p_shape; + + for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { + E_source->get()->notify_tile_data_properties_should_change(); } - int id = String::to_int(n.get_data(), slash); - - if (!tile_map.has(id)) { - create_tile(id); - } - String what = n.substr(slash + 1, n.length()); - - if (what == "name") { - tile_set_name(id, p_value); - } else if (what == "texture") { - tile_set_texture(id, p_value); - } else if (what == "tex_offset") { - tile_set_texture_offset(id, p_value); - } else if (what == "material") { - tile_set_material(id, p_value); - } else if (what == "modulate") { - tile_set_modulate(id, p_value); - } else if (what == "region") { - tile_set_region(id, p_value); - } else if (what == "tile_mode") { - tile_set_tile_mode(id, (TileMode)((int)p_value)); - } else if (what == "is_autotile") { - // backward compatibility for Godot 3.0.x - // autotile used to be a bool, it's now an enum - bool is_autotile = p_value; - if (is_autotile) { - tile_set_tile_mode(id, AUTO_TILE); - } - } else if (what.left(9) == "autotile/") { - what = what.right(9); - if (what == "bitmask_mode") { - autotile_set_bitmask_mode(id, (BitmaskMode)((int)p_value)); - } else if (what == "icon_coordinate") { - autotile_set_icon_coordinate(id, p_value); - } else if (what == "tile_size") { - autotile_set_size(id, p_value); - } else if (what == "spacing") { - autotile_set_spacing(id, p_value); - } else if (what == "bitmask_flags") { - tile_map[id].autotile_data.flags.clear(); - if (p_value.is_array()) { - Array p = p_value; - Vector2 last_coord; - while (p.size() > 0) { - if (p[0].get_type() == Variant::VECTOR2) { - last_coord = p[0]; - } else if (p[0].get_type() == Variant::INT) { - autotile_set_bitmask(id, last_coord, p[0]); - } - p.pop_front(); - } - } - } else if (what == "occluder_map") { - tile_map[id].autotile_data.occluder_map.clear(); - Array p = p_value; - Vector2 last_coord; - while (p.size() > 0) { - if (p[0].get_type() == Variant::VECTOR2) { - last_coord = p[0]; - } else if (p[0].get_type() == Variant::OBJECT) { - autotile_set_light_occluder(id, p[0], last_coord); - } - p.pop_front(); - } - } else if (what == "navpoly_map") { - tile_map[id].autotile_data.navpoly_map.clear(); - Array p = p_value; - Vector2 last_coord; - while (p.size() > 0) { - if (p[0].get_type() == Variant::VECTOR2) { - last_coord = p[0]; - } else if (p[0].get_type() == Variant::OBJECT) { - autotile_set_navigation_polygon(id, p[0], last_coord); - } - p.pop_front(); - } - } else if (what == "priority_map") { - tile_map[id].autotile_data.priority_map.clear(); - Array p = p_value; - Vector3 val; - Vector2 v; - int priority; - while (p.size() > 0) { - val = p[0]; - if (val.z > 1) { - v.x = val.x; - v.y = val.y; - priority = (int)val.z; - tile_map[id].autotile_data.priority_map[v] = priority; - } - p.pop_front(); - } - } else if (what == "z_index_map") { - tile_map[id].autotile_data.z_index_map.clear(); - Array p = p_value; - Vector3 val; - Vector2 v; - int z_index; - while (p.size() > 0) { - val = p[0]; - if (val.z != 0) { - v.x = val.x; - v.y = val.y; - z_index = (int)val.z; - tile_map[id].autotile_data.z_index_map[v] = z_index; - } - p.pop_front(); - } - } - } else if (what == "shape") { - if (tile_get_shape_count(id) > 0) { - for (int i = 0; i < tile_get_shape_count(id); i++) { - tile_set_shape(id, i, p_value); - } - } else { - tile_set_shape(id, 0, p_value); - } - } else if (what == "shape_offset") { - if (tile_get_shape_count(id) > 0) { - for (int i = 0; i < tile_get_shape_count(id); i++) { - tile_set_shape_offset(id, i, p_value); - } - } else { - tile_set_shape_offset(id, 0, p_value); - } - } else if (what == "shape_transform") { - if (tile_get_shape_count(id) > 0) { - for (int i = 0; i < tile_get_shape_count(id); i++) { - tile_set_shape_transform(id, i, p_value); - } - } else { - tile_set_shape_transform(id, 0, p_value); - } - } else if (what == "shape_one_way") { - if (tile_get_shape_count(id) > 0) { - for (int i = 0; i < tile_get_shape_count(id); i++) { - tile_set_shape_one_way(id, i, p_value); - } - } else { - tile_set_shape_one_way(id, 0, p_value); - } - } else if (what == "shape_one_way_margin") { - if (tile_get_shape_count(id) > 0) { - for (int i = 0; i < tile_get_shape_count(id); i++) { - tile_set_shape_one_way_margin(id, i, p_value); - } - } else { - tile_set_shape_one_way_margin(id, 0, p_value); - } - } else if (what == "shapes") { - _tile_set_shapes(id, p_value); - } else if (what == "occluder") { - tile_set_light_occluder(id, p_value); - } else if (what == "occluder_offset") { - tile_set_occluder_offset(id, p_value); - } else if (what == "navigation") { - 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; + + terrain_bits_meshes_dirty = true; + tile_meshes_dirty = true; + emit_changed(); +} +TileSet::TileShape TileSet::get_tile_shape() const { + return tile_shape; +} + +void TileSet::set_tile_layout(TileSet::TileLayout p_layout) { + tile_layout = p_layout; + emit_changed(); +} +TileSet::TileLayout TileSet::get_tile_layout() const { + return tile_layout; +} + +void TileSet::set_tile_offset_axis(TileSet::TileOffsetAxis p_alignment) { + tile_offset_axis = p_alignment; + + for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { + E_source->get()->notify_tile_data_properties_should_change(); } - return true; + terrain_bits_meshes_dirty = true; + tile_meshes_dirty = true; + emit_changed(); +} +TileSet::TileOffsetAxis TileSet::get_tile_offset_axis() const { + return tile_offset_axis; } -bool TileSet::_get(const StringName &p_name, Variant &r_ret) const { - String n = p_name; - int slash = n.find("/"); - if (slash == -1) { - return false; +void TileSet::set_tile_size(Size2i p_size) { + ERR_FAIL_COND(p_size.x < 1 || p_size.y < 1); + tile_size = p_size; + terrain_bits_meshes_dirty = true; + tile_meshes_dirty = true; + emit_changed(); +} +Size2i TileSet::get_tile_size() const { + return tile_size; +} + +int TileSet::get_next_source_id() const { + return next_source_id; +} + +void TileSet::_compute_next_source_id() { + while (sources.has(next_source_id)) { + next_source_id = (next_source_id + 1) % 1073741824; // 2 ** 30 + }; +} + +// Sources management +int TileSet::add_source(Ref<TileSetSource> p_tile_set_source, int p_atlas_source_id_override) { + ERR_FAIL_COND_V(!p_tile_set_source.is_valid(), -1); + ERR_FAIL_COND_V_MSG(p_atlas_source_id_override >= 0 && (sources.has(p_atlas_source_id_override)), -1, vformat("Cannot create TileSet atlas source. Another atlas source exists with id %d.", p_atlas_source_id_override)); + + int new_source_id = p_atlas_source_id_override >= 0 ? p_atlas_source_id_override : next_source_id; + sources[new_source_id] = p_tile_set_source; + source_ids.append(new_source_id); + source_ids.sort(); + p_tile_set_source->set_tile_set(this); + _compute_next_source_id(); + + sources[new_source_id]->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileSet::_source_changed)); + + emit_changed(); + + return new_source_id; +} + +void TileSet::remove_source(int p_source_id) { + ERR_FAIL_COND_MSG(!sources.has(p_source_id), vformat("Cannot remove TileSet atlas source. No tileset atlas source with id %d.", p_source_id)); + + sources[p_source_id]->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileSet::_source_changed)); + + sources[p_source_id]->set_tile_set(nullptr); + sources.erase(p_source_id); + source_ids.erase(p_source_id); + source_ids.sort(); + + emit_changed(); +} + +void TileSet::set_source_id(int p_source_id, int p_new_source_id) { + ERR_FAIL_COND(p_new_source_id < 0); + ERR_FAIL_COND_MSG(!sources.has(p_source_id), vformat("Cannot change TileSet atlas source ID. No tileset atlas source with id %d.", p_source_id)); + if (p_source_id == p_new_source_id) { + return; } - int id = String::to_int(n.get_data(), slash); - - ERR_FAIL_COND_V(!tile_map.has(id), false); - - String what = n.substr(slash + 1, n.length()); - - if (what == "name") { - r_ret = tile_get_name(id); - } else if (what == "texture") { - r_ret = tile_get_texture(id); - } else if (what == "tex_offset") { - r_ret = tile_get_texture_offset(id); - } else if (what == "material") { - r_ret = tile_get_material(id); - } else if (what == "modulate") { - r_ret = tile_get_modulate(id); - } else if (what == "region") { - r_ret = tile_get_region(id); - } else if (what == "tile_mode") { - r_ret = tile_get_tile_mode(id); - } else if (what.left(9) == "autotile/") { - what = what.right(9); - if (what == "bitmask_mode") { - r_ret = autotile_get_bitmask_mode(id); - } else if (what == "icon_coordinate") { - r_ret = autotile_get_icon_coordinate(id); - } else if (what == "tile_size") { - r_ret = autotile_get_size(id); - } else if (what == "spacing") { - r_ret = autotile_get_spacing(id); - } else if (what == "bitmask_flags") { - Array p; - for (Map<Vector2, uint32_t>::Element *E = tile_map[id].autotile_data.flags.front(); E; E = E->next()) { - p.push_back(E->key()); - p.push_back(E->value()); - } - r_ret = p; - } else if (what == "occluder_map") { - Array p; - for (Map<Vector2, Ref<OccluderPolygon2D>>::Element *E = tile_map[id].autotile_data.occluder_map.front(); E; E = E->next()) { - p.push_back(E->key()); - p.push_back(E->value()); - } - r_ret = p; - } else if (what == "navpoly_map") { - Array p; - for (Map<Vector2, Ref<NavigationPolygon>>::Element *E = tile_map[id].autotile_data.navpoly_map.front(); E; E = E->next()) { - p.push_back(E->key()); - p.push_back(E->value()); - } - r_ret = p; - } else if (what == "priority_map") { - Array p; - Vector3 v; - for (Map<Vector2, int>::Element *E = tile_map[id].autotile_data.priority_map.front(); E; E = E->next()) { - if (E->value() > 1) { - //Don't save default value - v.x = E->key().x; - v.y = E->key().y; - v.z = E->value(); - p.push_back(v); - } - } - r_ret = p; - } else if (what == "z_index_map") { - Array p; - Vector3 v; - for (Map<Vector2, int>::Element *E = tile_map[id].autotile_data.z_index_map.front(); E; E = E->next()) { - if (E->value() != 0) { - //Don't save default value - v.x = E->key().x; - v.y = E->key().y; - v.z = E->value(); - p.push_back(v); - } - } - r_ret = p; - } - } else if (what == "shape") { - r_ret = tile_get_shape(id, 0); - } else if (what == "shape_offset") { - r_ret = tile_get_shape_offset(id, 0); - } else if (what == "shape_transform") { - r_ret = tile_get_shape_transform(id, 0); - } else if (what == "shape_one_way") { - r_ret = tile_get_shape_one_way(id, 0); - } else if (what == "shape_one_way_margin") { - r_ret = tile_get_shape_one_way_margin(id, 0); - } else if (what == "shapes") { - r_ret = _tile_get_shapes(id); - } else if (what == "occluder") { - r_ret = tile_get_light_occluder(id); - } else if (what == "occluder_offset") { - r_ret = tile_get_occluder_offset(id); - } else if (what == "navigation") { - 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; + + ERR_FAIL_COND_MSG(sources.has(p_new_source_id), vformat("Cannot change TileSet atlas source ID. Another atlas source exists with id %d.", p_new_source_id)); + + sources[p_new_source_id] = sources[p_source_id]; + sources.erase(p_source_id); + + source_ids.erase(p_source_id); + source_ids.append(p_new_source_id); + source_ids.sort(); + + emit_changed(); +} + +bool TileSet::has_source(int p_source_id) const { + return sources.has(p_source_id); +} + +Ref<TileSetSource> TileSet::get_source(int p_source_id) const { + ERR_FAIL_COND_V_MSG(!sources.has(p_source_id), nullptr, vformat("No TileSet atlas source with id %d.", p_source_id)); + + return sources[p_source_id]; +} + +int TileSet::get_source_count() const { + return source_ids.size(); +} + +int TileSet::get_source_id(int p_index) const { + ERR_FAIL_INDEX_V(p_index, source_ids.size(), -1); + return source_ids[p_index]; +} + +// Rendering +void TileSet::set_uv_clipping(bool p_uv_clipping) { + if (uv_clipping == p_uv_clipping) { + return; } + uv_clipping = p_uv_clipping; + emit_changed(); +} +bool TileSet::is_uv_clipping() const { + return uv_clipping; +}; - return true; +void TileSet::set_y_sorting(bool p_y_sort) { + if (y_sorting == p_y_sort) { + return; + } + y_sorting = p_y_sort; + emit_changed(); } +bool TileSet::is_y_sorting() const { + return y_sorting; +}; + +void TileSet::set_occlusion_layers_count(int p_occlusion_layers_count) { + ERR_FAIL_COND(p_occlusion_layers_count < 0); + if (occlusion_layers.size() == p_occlusion_layers_count) { + return; + } + + occlusion_layers.resize(p_occlusion_layers_count); + + for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { + E_source->get()->notify_tile_data_properties_should_change(); + } -void TileSet::_get_property_list(List<PropertyInfo> *p_list) const { - for (Map<int, TileData>::Element *E = tile_map.front(); E; E = E->next()) { - int id = E->key(); - String pre = itos(id) + "/"; - p_list->push_back(PropertyInfo(Variant::STRING, pre + "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); - p_list->push_back(PropertyInfo(Variant::OBJECT, pre + "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_NOEDITOR)); - p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "tex_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); - p_list->push_back(PropertyInfo(Variant::OBJECT, pre + "material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial", PROPERTY_USAGE_NOEDITOR)); - p_list->push_back(PropertyInfo(Variant::COLOR, pre + "modulate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); - p_list->push_back(PropertyInfo(Variant::RECT2, pre + "region", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); - p_list->push_back(PropertyInfo(Variant::INT, pre + "tile_mode", PROPERTY_HINT_ENUM, "SINGLE_TILE,AUTO_TILE,ATLAS_TILE", PROPERTY_USAGE_NOEDITOR)); - if (tile_get_tile_mode(id) == AUTO_TILE) { - 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)); - p_list->push_back(PropertyInfo(Variant::INT, pre + "autotile/spacing", PROPERTY_HINT_RANGE, "0,256,1", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); - p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/occluder_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); - p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/navpoly_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); - p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/priority_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); - p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/z_index_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); - } else if (tile_get_tile_mode(id) == ATLAS_TILE) { - 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)); - p_list->push_back(PropertyInfo(Variant::INT, pre + "autotile/spacing", PROPERTY_HINT_RANGE, "0,256,1", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); - p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/occluder_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); - p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/navpoly_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); - p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/priority_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); - p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/z_index_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); - } - p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "occluder_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); - p_list->push_back(PropertyInfo(Variant::OBJECT, pre + "occluder", PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D", PROPERTY_USAGE_NOEDITOR)); - p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "navigation_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); - p_list->push_back(PropertyInfo(Variant::OBJECT, pre + "navigation", PROPERTY_HINT_RESOURCE_TYPE, "NavigationPolygon", PROPERTY_USAGE_NOEDITOR)); - p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "shape_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); - p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "shape_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); - p_list->push_back(PropertyInfo(Variant::OBJECT, pre + "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape2D", PROPERTY_USAGE_NOEDITOR)); - p_list->push_back(PropertyInfo(Variant::BOOL, pre + "shape_one_way", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); - p_list->push_back(PropertyInfo(Variant::FLOAT, pre + "shape_one_way_margin", PROPERTY_HINT_RANGE, "0,128,0.01", PROPERTY_USAGE_NOEDITOR)); - 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(RS::CANVAS_ITEM_Z_MIN) + "," + itos(RS::CANVAS_ITEM_Z_MAX) + ",1", PROPERTY_USAGE_NOEDITOR)); - } -} - -void TileSet::create_tile(int p_id) { - ERR_FAIL_COND(tile_map.has(p_id)); - tile_map[p_id] = TileData(); - tile_map[p_id].autotile_data = AutotileData(); notify_property_list_changed(); emit_changed(); } -void TileSet::autotile_set_bitmask_mode(int p_id, BitmaskMode p_mode) { - ERR_FAIL_COND(!tile_map.has(p_id)); - tile_map[p_id].autotile_data.bitmask_mode = p_mode; - notify_property_list_changed(); +int TileSet::get_occlusion_layers_count() const { + return occlusion_layers.size(); +}; + +void TileSet::set_occlusion_layer_light_mask(int p_layer_index, int p_light_mask) { + ERR_FAIL_INDEX(p_layer_index, occlusion_layers.size()); + occlusion_layers.write[p_layer_index].light_mask = p_light_mask; emit_changed(); } -TileSet::BitmaskMode TileSet::autotile_get_bitmask_mode(int p_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), BITMASK_2X2); - return tile_map[p_id].autotile_data.bitmask_mode; +int TileSet::get_occlusion_layer_light_mask(int p_layer_index) const { + ERR_FAIL_INDEX_V(p_layer_index, occlusion_layers.size(), 0); + return occlusion_layers[p_layer_index].light_mask; } -void TileSet::tile_set_texture(int p_id, const Ref<Texture2D> &p_texture) { - ERR_FAIL_COND(!tile_map.has(p_id)); - tile_map[p_id].texture = p_texture; +void TileSet::set_occlusion_layer_sdf_collision(int p_layer_index, int p_sdf_collision) { + ERR_FAIL_INDEX(p_layer_index, occlusion_layers.size()); + occlusion_layers.write[p_layer_index].sdf_collision = p_sdf_collision; emit_changed(); } -Ref<Texture2D> TileSet::tile_get_texture(int p_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), Ref<Texture2D>()); - return tile_map[p_id].texture; +bool TileSet::get_occlusion_layer_sdf_collision(int p_layer_index) const { + ERR_FAIL_INDEX_V(p_layer_index, occlusion_layers.size(), false); + return occlusion_layers[p_layer_index].sdf_collision; } -void TileSet::tile_set_material(int p_id, const Ref<ShaderMaterial> &p_material) { - ERR_FAIL_COND(!tile_map.has(p_id)); - tile_map[p_id].material = p_material; +// Physics +void TileSet::set_physics_layers_count(int p_physics_layers_count) { + ERR_FAIL_COND(p_physics_layers_count < 0); + if (physics_layers.size() == p_physics_layers_count) { + return; + } + + physics_layers.resize(p_physics_layers_count); + + for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { + E_source->get()->notify_tile_data_properties_should_change(); + } + + notify_property_list_changed(); emit_changed(); } -Ref<ShaderMaterial> TileSet::tile_get_material(int p_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), Ref<ShaderMaterial>()); - return tile_map[p_id].material; +int TileSet::get_physics_layers_count() const { + return physics_layers.size(); } -void TileSet::tile_set_modulate(int p_id, const Color &p_modulate) { - ERR_FAIL_COND(!tile_map.has(p_id)); - tile_map[p_id].modulate = p_modulate; +void TileSet::set_physics_layer_collision_layer(int p_layer_index, uint32_t p_layer) { + ERR_FAIL_INDEX(p_layer_index, physics_layers.size()); + physics_layers.write[p_layer_index].collision_layer = p_layer; emit_changed(); } -Color TileSet::tile_get_modulate(int p_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), Color(1, 1, 1)); - return tile_map[p_id].modulate; +uint32_t TileSet::get_physics_layer_collision_layer(int p_layer_index) const { + ERR_FAIL_INDEX_V(p_layer_index, physics_layers.size(), 0); + return physics_layers[p_layer_index].collision_layer; } -void TileSet::tile_set_texture_offset(int p_id, const Vector2 &p_offset) { - ERR_FAIL_COND(!tile_map.has(p_id)); - tile_map[p_id].offset = p_offset; +void TileSet::set_physics_layer_collision_mask(int p_layer_index, uint32_t p_mask) { + ERR_FAIL_INDEX(p_layer_index, physics_layers.size()); + physics_layers.write[p_layer_index].collision_mask = p_mask; emit_changed(); } -Vector2 TileSet::tile_get_texture_offset(int p_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), Vector2()); - return tile_map[p_id].offset; +uint32_t TileSet::get_physics_layer_collision_mask(int p_layer_index) const { + ERR_FAIL_INDEX_V(p_layer_index, physics_layers.size(), 0); + return physics_layers[p_layer_index].collision_mask; +} + +void TileSet::set_physics_layer_physics_material(int p_layer_index, Ref<PhysicsMaterial> p_physics_material) { + ERR_FAIL_INDEX(p_layer_index, physics_layers.size()); + physics_layers.write[p_layer_index].physics_material = p_physics_material; } -void TileSet::tile_set_region(int p_id, const Rect2 &p_region) { - ERR_FAIL_COND(!tile_map.has(p_id)); - tile_map[p_id].region = p_region; +Ref<PhysicsMaterial> TileSet::get_physics_layer_physics_material(int p_layer_index) const { + ERR_FAIL_INDEX_V(p_layer_index, physics_layers.size(), Ref<PhysicsMaterial>()); + return physics_layers[p_layer_index].physics_material; +} + +// Terrains +void TileSet::set_terrain_sets_count(int p_terrains_sets_count) { + ERR_FAIL_COND(p_terrains_sets_count < 0); + + terrain_sets.resize(p_terrains_sets_count); + + notify_property_list_changed(); emit_changed(); } -Rect2 TileSet::tile_get_region(int p_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), Rect2()); - return tile_map[p_id].region; +int TileSet::get_terrain_sets_count() const { + return terrain_sets.size(); } -void TileSet::tile_set_tile_mode(int p_id, TileMode p_tile_mode) { - ERR_FAIL_COND(!tile_map.has(p_id)); - tile_map[p_id].tile_mode = p_tile_mode; +void TileSet::set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode) { + ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); + terrain_sets.write[p_terrain_set].mode = p_terrain_mode; + for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { + E_source->get()->notify_tile_data_properties_should_change(); + } + + notify_property_list_changed(); emit_changed(); } -TileSet::TileMode TileSet::tile_get_tile_mode(int p_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), SINGLE_TILE); - return tile_map[p_id].tile_mode; +TileSet::TerrainMode TileSet::get_terrain_set_mode(int p_terrain_set) const { + ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES); + return terrain_sets[p_terrain_set].mode; } -void TileSet::autotile_set_icon_coordinate(int p_id, Vector2 coord) { - ERR_FAIL_COND(!tile_map.has(p_id)); - tile_map[p_id].autotile_data.icon_coord = coord; +void TileSet::set_terrains_count(int p_terrain_set, int p_terrains_layers_count) { + ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); + ERR_FAIL_COND(p_terrains_layers_count < 0); + if (terrain_sets[p_terrain_set].terrains.size() == p_terrains_layers_count) { + return; + } + + int old_size = terrain_sets[p_terrain_set].terrains.size(); + terrain_sets.write[p_terrain_set].terrains.resize(p_terrains_layers_count); + + // Default name and color + for (int i = old_size; i < terrain_sets.write[p_terrain_set].terrains.size(); i++) { + float hue_rotate = (i * 2 % 16) / 16.0; + Color c; + c.set_hsv(Math::fmod(float(hue_rotate), float(1.0)), 0.5, 0.5); + terrain_sets.write[p_terrain_set].terrains.write[i].color = c; + terrain_sets.write[p_terrain_set].terrains.write[i].name = String(vformat("Terrain %d", i)); + } + + for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { + E_source->get()->notify_tile_data_properties_should_change(); + } + + notify_property_list_changed(); emit_changed(); } -Vector2 TileSet::autotile_get_icon_coordinate(int p_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), Vector2()); - return tile_map[p_id].autotile_data.icon_coord; +int TileSet::get_terrains_count(int p_terrain_set) const { + ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), -1); + return terrain_sets[p_terrain_set].terrains.size(); } -void TileSet::autotile_set_spacing(int p_id, int p_spacing) { - ERR_FAIL_COND(!tile_map.has(p_id)); - ERR_FAIL_COND(p_spacing < 0); - tile_map[p_id].autotile_data.spacing = p_spacing; +void TileSet::set_terrain_name(int p_terrain_set, int p_terrain_index, String p_name) { + ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); + ERR_FAIL_INDEX(p_terrain_index, terrain_sets[p_terrain_set].terrains.size()); + terrain_sets.write[p_terrain_set].terrains.write[p_terrain_index].name = p_name; emit_changed(); } -int TileSet::autotile_get_spacing(int p_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), 0); - return tile_map[p_id].autotile_data.spacing; +String TileSet::get_terrain_name(int p_terrain_set, int p_terrain_index) const { + ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), String()); + ERR_FAIL_INDEX_V(p_terrain_index, terrain_sets[p_terrain_set].terrains.size(), String()); + return terrain_sets[p_terrain_set].terrains[p_terrain_index].name; } -void TileSet::autotile_set_size(int p_id, Size2 p_size) { - ERR_FAIL_COND(!tile_map.has(p_id)); - ERR_FAIL_COND(p_size.x <= 0 || p_size.y <= 0); - tile_map[p_id].autotile_data.size = p_size; +void TileSet::set_terrain_color(int p_terrain_set, int p_terrain_index, Color p_color) { + ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); + ERR_FAIL_INDEX(p_terrain_index, terrain_sets[p_terrain_set].terrains.size()); + if (p_color.a != 1.0) { + WARN_PRINT("Terrain color should have alpha == 1.0"); + p_color.a = 1.0; + } + terrain_sets.write[p_terrain_set].terrains.write[p_terrain_index].color = p_color; + emit_changed(); } -Size2 TileSet::autotile_get_size(int p_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), Size2()); - return tile_map[p_id].autotile_data.size; +Color TileSet::get_terrain_color(int p_terrain_set, int p_terrain_index) const { + ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), Color()); + ERR_FAIL_INDEX_V(p_terrain_index, terrain_sets[p_terrain_set].terrains.size(), Color()); + return terrain_sets[p_terrain_set].terrains[p_terrain_index].color; } -void TileSet::autotile_clear_bitmask_map(int p_id) { - ERR_FAIL_COND(!tile_map.has(p_id)); - tile_map[p_id].autotile_data.flags.clear(); +bool TileSet::is_valid_peering_bit_for_mode(TileSet::TerrainMode p_terrain_mode, TileSet::CellNeighbor p_peering_bit) const { + if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { + if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) { + if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_LEFT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_SIDE) { + return true; + } + } + if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER) { + return true; + } + } + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) { + if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return true; + } + } + if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_LEFT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return true; + } + } + } else { + if (get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) { + if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_LEFT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return true; + } + } + if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER) { + return true; + } + } + } else { + if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) { + if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return true; + } + } + if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_LEFT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER) { + return true; + } + } + } + } + return false; } -void TileSet::autotile_set_subtile_priority(int p_id, const Vector2 &p_coord, int p_priority) { - ERR_FAIL_COND(!tile_map.has(p_id)); - ERR_FAIL_COND(p_priority <= 0); - tile_map[p_id].autotile_data.priority_map[p_coord] = p_priority; +bool TileSet::is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const { + if (p_terrain_set < 0 || p_terrain_set >= get_terrain_sets_count()) { + return false; + } + + TileSet::TerrainMode terrain_mode = get_terrain_set_mode(p_terrain_set); + return is_valid_peering_bit_for_mode(terrain_mode, p_peering_bit); } -int TileSet::autotile_get_subtile_priority(int p_id, const Vector2 &p_coord) { - ERR_FAIL_COND_V(!tile_map.has(p_id), 1); - if (tile_map[p_id].autotile_data.priority_map.has(p_coord)) { - return tile_map[p_id].autotile_data.priority_map[p_coord]; +// Navigation +void TileSet::set_navigation_layers_count(int p_navigation_layers_count) { + ERR_FAIL_COND(p_navigation_layers_count < 0); + if (navigation_layers.size() == p_navigation_layers_count) { + return; } - //When not custom priority set return the default value - return 1; + + navigation_layers.resize(p_navigation_layers_count); + + for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { + E_source->get()->notify_tile_data_properties_should_change(); + } + + notify_property_list_changed(); + emit_changed(); } -const Map<Vector2, int> &TileSet::autotile_get_priority_map(int p_id) const { - static Map<Vector2, int> dummy; - ERR_FAIL_COND_V(!tile_map.has(p_id), dummy); - return tile_map[p_id].autotile_data.priority_map; +int TileSet::get_navigation_layers_count() const { + return navigation_layers.size(); } -void TileSet::autotile_set_z_index(int p_id, const Vector2 &p_coord, int p_z_index) { - ERR_FAIL_COND(!tile_map.has(p_id)); - tile_map[p_id].autotile_data.z_index_map[p_coord] = p_z_index; +void TileSet::set_navigation_layer_layers(int p_layer_index, uint32_t p_layers) { + ERR_FAIL_INDEX(p_layer_index, navigation_layers.size()); + navigation_layers.write[p_layer_index].layers = p_layers; emit_changed(); } -int TileSet::autotile_get_z_index(int p_id, const Vector2 &p_coord) { - ERR_FAIL_COND_V(!tile_map.has(p_id), 1); - if (tile_map[p_id].autotile_data.z_index_map.has(p_coord)) { - return tile_map[p_id].autotile_data.z_index_map[p_coord]; +uint32_t TileSet::get_navigation_layer_layers(int p_layer_index) const { + ERR_FAIL_INDEX_V(p_layer_index, navigation_layers.size(), 0); + return navigation_layers[p_layer_index].layers; +} + +// Custom data. +void TileSet::set_custom_data_layers_count(int p_custom_data_layers_count) { + ERR_FAIL_COND(p_custom_data_layers_count < 0); + if (custom_data_layers.size() == p_custom_data_layers_count) { + return; } - //When not custom z index set return the default value - return 0; + + custom_data_layers.resize(p_custom_data_layers_count); + + for (Map<String, int>::Element *E = custom_data_layers_by_name.front(); E; E = E->next()) { + if (E->get() >= custom_data_layers.size()) { + custom_data_layers_by_name.erase(E); + } + } + + for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { + E_source->get()->notify_tile_data_properties_should_change(); + } + + notify_property_list_changed(); + emit_changed(); } -const Map<Vector2, int> &TileSet::autotile_get_z_index_map(int p_id) const { - static Map<Vector2, int> dummy; - ERR_FAIL_COND_V(!tile_map.has(p_id), dummy); - return tile_map[p_id].autotile_data.z_index_map; +int TileSet::get_custom_data_layers_count() const { + return custom_data_layers.size(); } -void TileSet::autotile_set_bitmask(int p_id, Vector2 p_coord, uint32_t p_flag) { - ERR_FAIL_COND(!tile_map.has(p_id)); - if (p_flag == 0) { - if (tile_map[p_id].autotile_data.flags.has(p_coord)) { - tile_map[p_id].autotile_data.flags.erase(p_coord); +int TileSet::get_custom_data_layer_by_name(String p_value) const { + if (custom_data_layers_by_name.has(p_value)) { + return custom_data_layers_by_name[p_value]; + } else { + return -1; + } +} + +void TileSet::set_custom_data_name(int p_layer_id, String p_value) { + ERR_FAIL_INDEX(p_layer_id, custom_data_layers.size()); + + // Exit if another property has the same name. + if (!p_value.is_empty()) { + for (int other_layer_id = 0; other_layer_id < get_custom_data_layers_count(); other_layer_id++) { + if (other_layer_id != p_layer_id && get_custom_data_name(other_layer_id) == p_value) { + ERR_FAIL_MSG(vformat("There is already a custom property named %s", p_value)); + } } + } + + if (p_value.is_empty() && custom_data_layers_by_name.has(p_value)) { + custom_data_layers_by_name.erase(p_value); } else { - tile_map[p_id].autotile_data.flags[p_coord] = p_flag; + custom_data_layers_by_name[p_value] = p_layer_id; } + + custom_data_layers.write[p_layer_id].name = p_value; + emit_changed(); +} + +String TileSet::get_custom_data_name(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, custom_data_layers.size(), ""); + return custom_data_layers[p_layer_id].name; } -uint32_t TileSet::autotile_get_bitmask(int p_id, Vector2 p_coord) { - ERR_FAIL_COND_V(!tile_map.has(p_id), 0); - if (!tile_map[p_id].autotile_data.flags.has(p_coord)) { - return 0; +void TileSet::set_custom_data_type(int p_layer_id, Variant::Type p_value) { + ERR_FAIL_INDEX(p_layer_id, custom_data_layers.size()); + custom_data_layers.write[p_layer_id].type = p_value; + + for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { + E_source->get()->notify_tile_data_properties_should_change(); } - return tile_map[p_id].autotile_data.flags[p_coord]; + + emit_changed(); +} + +Variant::Type TileSet::get_custom_data_type(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, custom_data_layers.size(), Variant::NIL); + return custom_data_layers[p_layer_id].type; } -const Map<Vector2, uint32_t> &TileSet::autotile_get_bitmask_map(int p_id) { - static Map<Vector2, uint32_t> dummy; - static Map<Vector2, uint32_t> dummy_atlas; - ERR_FAIL_COND_V(!tile_map.has(p_id), dummy); - if (tile_get_tile_mode(p_id) == ATLAS_TILE) { - dummy_atlas = Map<Vector2, uint32_t>(); - Rect2 region = tile_get_region(p_id); - Size2 size = autotile_get_size(p_id); - float spacing = autotile_get_spacing(p_id); - for (int x = 0; x < (region.size.x / (size.x + spacing)); x++) { - for (int y = 0; y < (region.size.y / (size.y + spacing)); y++) { - dummy_atlas.insert(Vector2(x, y), 0); +Vector<Vector2> TileSet::get_tile_shape_polygon() { + Vector<Vector2> points; + if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { + points.append(Vector2(0.0, 0.0)); + points.append(Vector2(1.0, 0.0)); + points.append(Vector2(1.0, 1.0)); + points.append(Vector2(0.0, 1.0)); + } else { + float overlap = 0.0; + switch (tile_shape) { + case TileSet::TILE_SHAPE_ISOMETRIC: + overlap = 0.5; + break; + case TileSet::TILE_SHAPE_HEXAGON: + overlap = 0.25; + break; + case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: + overlap = 0.0; + break; + default: + break; + } + + points.append(Vector2(0.5, 0.0)); + points.append(Vector2(0.0, overlap)); + points.append(Vector2(0.0, 1.0 - overlap)); + points.append(Vector2(0.5, 1.0)); + points.append(Vector2(1.0, 1.0 - overlap)); + points.append(Vector2(1.0, overlap)); + points.append(Vector2(0.5, 0.0)); + if (get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL) { + for (int i = 0; i < points.size(); i++) { + points.write[i] = Vector2(points[i].y, points[i].x); } } - return dummy_atlas; + } + for (int i = 0; i < points.size(); i++) { + points.write[i] = points[i] * tile_size - tile_size / 2; + } + return points; +} + +void TileSet::draw_tile_shape(CanvasItem *p_canvas_item, Rect2 p_region, Color p_color, bool p_filled, Ref<Texture2D> p_texture) { + if (tile_meshes_dirty) { + Vector<Vector2> uvs = get_tile_shape_polygon(); + for (int i = 0; i < uvs.size(); i++) { + uvs.write[i] = (uvs[i] + tile_size / 2) / tile_size; + } + + Vector<Color> colors; + colors.resize(uvs.size()); + colors.fill(Color(1.0, 1.0, 1.0, 1.0)); + + // Filled mesh. + tile_filled_mesh->clear_surfaces(); + Array a; + a.resize(Mesh::ARRAY_MAX); + a[Mesh::ARRAY_VERTEX] = uvs; + a[Mesh::ARRAY_TEX_UV] = uvs; + a[Mesh::ARRAY_COLOR] = colors; + a[Mesh::ARRAY_INDEX] = Geometry2D::triangulate_polygon(uvs); + tile_filled_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); + + // Lines mesh. + tile_lines_mesh->clear_surfaces(); + a.clear(); + a.resize(Mesh::ARRAY_MAX); + // Add the first point again when drawing lines. + uvs.push_back(uvs[0]); + colors.push_back(colors[0]); + a[Mesh::ARRAY_VERTEX] = uvs; + a[Mesh::ARRAY_COLOR] = colors; + tile_lines_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINE_STRIP, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); + + tile_meshes_dirty = false; + } + + Transform2D xform; + xform.scale(p_region.size); + xform.set_origin(p_region.get_position()); + if (p_filled) { + p_canvas_item->draw_mesh(tile_filled_mesh, p_texture, xform, p_color); } else { - return tile_map[p_id].autotile_data.flags; + p_canvas_item->draw_mesh(tile_lines_mesh, Ref<Texture2D>(), xform, p_color); } } -Vector2 TileSet::autotile_get_subtile_for_bitmask(int p_id, uint16_t p_bitmask, const Node *p_tilemap_node, const Vector2 &p_tile_location) { - ERR_FAIL_COND_V(!tile_map.has(p_id), Vector2()); - //First try to forward selection to script - if (p_tilemap_node->get_class_name() == "TileMap") { - if (get_script_instance() != nullptr) { - if (get_script_instance()->has_method("_forward_subtile_selection")) { - Variant ret = get_script_instance()->call("_forward_subtile_selection", p_id, p_bitmask, p_tilemap_node, p_tile_location); - if (ret.get_type() == Variant::VECTOR2) { - return ret; +Vector<Point2> TileSet::get_terrain_bit_polygon(int p_terrain_set, TileSet::CellNeighbor p_bit) { + ERR_FAIL_COND_V(p_terrain_set < 0 || p_terrain_set >= get_terrain_sets_count(), Vector<Point2>()); + + TileSet::TerrainMode terrain_mode = get_terrain_set_mode(p_terrain_set); + + if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { + if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { + return _get_square_corner_or_side_terrain_bit_polygon(tile_size, p_bit); + } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + return _get_square_corner_terrain_bit_polygon(tile_size, p_bit); + } else { // TileData::TERRAIN_MODE_MATCH_SIDES + return _get_square_side_terrain_bit_polygon(tile_size, p_bit); + } + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { + return _get_isometric_corner_or_side_terrain_bit_polygon(tile_size, p_bit); + } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + return _get_isometric_corner_terrain_bit_polygon(tile_size, p_bit); + } else { // TileData::TERRAIN_MODE_MATCH_SIDES + return _get_isometric_side_terrain_bit_polygon(tile_size, p_bit); + } + } else { + float overlap = 0.0; + switch (tile_shape) { + case TileSet::TILE_SHAPE_HEXAGON: + overlap = 0.25; + break; + case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: + overlap = 0.0; + break; + default: + break; + } + if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { + return _get_half_offset_corner_or_side_terrain_bit_polygon(tile_size, p_bit, overlap, tile_offset_axis); + } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + return _get_half_offset_corner_terrain_bit_polygon(tile_size, p_bit, overlap, tile_offset_axis); + } else { // TileData::TERRAIN_MODE_MATCH_SIDES + return _get_half_offset_side_terrain_bit_polygon(tile_size, p_bit, overlap, tile_offset_axis); + } + } +} + +#define TERRAIN_ALPHA 0.6 + +void TileSet::draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, const TileData *p_tile_data) { + ERR_FAIL_COND(!p_tile_data); + + if (terrain_bits_meshes_dirty) { + // Recompute the meshes. + terrain_bits_meshes.clear(); + + for (int terrain_mode_index = 0; terrain_mode_index < 3; terrain_mode_index++) { + TerrainMode terrain_mode = TerrainMode(terrain_mode_index); + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + CellNeighbor bit = CellNeighbor(i); + + if (is_valid_peering_bit_for_mode(terrain_mode, bit)) { + Vector<Vector2> polygon; + if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { + if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { + polygon = _get_square_corner_or_side_terrain_bit_polygon(tile_size, bit); + } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + polygon = _get_square_corner_terrain_bit_polygon(tile_size, bit); + } else { // TileData::TERRAIN_MODE_MATCH_SIDES + polygon = _get_square_side_terrain_bit_polygon(tile_size, bit); + } + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { + polygon = _get_isometric_corner_or_side_terrain_bit_polygon(tile_size, bit); + } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + polygon = _get_isometric_corner_terrain_bit_polygon(tile_size, bit); + } else { // TileData::TERRAIN_MODE_MATCH_SIDES + polygon = _get_isometric_side_terrain_bit_polygon(tile_size, bit); + } + } else { + float overlap = 0.0; + switch (tile_shape) { + case TileSet::TILE_SHAPE_HEXAGON: + overlap = 0.25; + break; + case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: + overlap = 0.0; + break; + default: + break; + } + if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { + polygon = _get_half_offset_corner_or_side_terrain_bit_polygon(tile_size, bit, overlap, tile_offset_axis); + } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + polygon = _get_half_offset_corner_terrain_bit_polygon(tile_size, bit, overlap, tile_offset_axis); + } else { // TileData::TERRAIN_MODE_MATCH_SIDES + polygon = _get_half_offset_side_terrain_bit_polygon(tile_size, bit, overlap, tile_offset_axis); + } + } + + Ref<ArrayMesh> mesh; + mesh.instantiate(); + Vector<Vector2> uvs; + uvs.resize(polygon.size()); + Vector<Color> colors; + colors.resize(polygon.size()); + colors.fill(Color(1.0, 1.0, 1.0, 1.0)); + Array a; + a.resize(Mesh::ARRAY_MAX); + a[Mesh::ARRAY_VERTEX] = polygon; + a[Mesh::ARRAY_TEX_UV] = uvs; + a[Mesh::ARRAY_COLOR] = colors; + a[Mesh::ARRAY_INDEX] = Geometry2D::triangulate_polygon(polygon); + mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); + terrain_bits_meshes[terrain_mode][bit] = mesh; } } } + terrain_bits_meshes_dirty = false; } - List<Vector2> coords; - List<uint32_t> priorities; - uint32_t priority_sum = 0; - uint32_t mask; - uint16_t mask_; - uint16_t mask_ignore; - for (Map<Vector2, uint32_t>::Element *E = tile_map[p_id].autotile_data.flags.front(); E; E = E->next()) { - mask = E->get(); - if (tile_map[p_id].autotile_data.bitmask_mode == BITMASK_2X2) { - mask |= (BIND_IGNORE_TOP | BIND_IGNORE_LEFT | BIND_IGNORE_CENTER | BIND_IGNORE_RIGHT | BIND_IGNORE_BOTTOM); + int terrain_set = p_tile_data->get_terrain_set(); + if (terrain_set < 0) { + return; + } + TileSet::TerrainMode terrain_mode = get_terrain_set_mode(terrain_set); + + RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + CellNeighbor bit = CellNeighbor(i); + if (is_valid_peering_bit_terrain(terrain_set, bit)) { + int terrain_id = p_tile_data->get_peering_bit_terrain(bit); + if (terrain_id >= 0) { + Color color = get_terrain_color(terrain_set, terrain_id); + color.a = TERRAIN_ALPHA; + p_canvas_item->draw_mesh(terrain_bits_meshes[terrain_mode][bit], Ref<Texture2D>(), Transform2D(), color); + } } + } + RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D()); +} - mask_ = mask & 0xFFFF; - mask_ignore = mask >> 16; +Vector<Vector<Ref<Texture2D>>> TileSet::generate_terrains_icons(Size2i p_size) { + // Counts the number of matching terrain tiles and find the best matching icon. + struct Count { + int count = 0; + float probability = 0.0; + Ref<Texture2D> texture; + Rect2i region; + }; + Vector<Vector<Ref<Texture2D>>> output; + LocalVector<LocalVector<Count>> counts; + output.resize(get_terrain_sets_count()); + counts.resize(get_terrain_sets_count()); + for (int terrain_set = 0; terrain_set < get_terrain_sets_count(); terrain_set++) { + output.write[terrain_set].resize(get_terrains_count(terrain_set)); + counts[terrain_set].resize(get_terrains_count(terrain_set)); + } - if (((mask_ & (~mask_ignore)) == (p_bitmask & (~mask_ignore))) && (((~mask_) | mask_ignore) == ((~p_bitmask) | mask_ignore))) { - uint32_t priority = autotile_get_subtile_priority(p_id, E->key()); - priority_sum += priority; - priorities.push_back(priority); - coords.push_back(E->key()); + for (int source_index = 0; source_index < get_source_count(); source_index++) { + int source_id = get_source_id(source_index); + Ref<TileSetSource> source = get_source(source_id); + + Ref<TileSetAtlasSource> atlas_source = source; + if (atlas_source.is_valid()) { + for (int tile_index = 0; tile_index < source->get_tiles_count(); tile_index++) { + Vector2i tile_id = source->get_tile_id(tile_index); + for (int alternative_index = 0; alternative_index < source->get_alternative_tiles_count(tile_id); alternative_index++) { + int alternative_id = source->get_alternative_tile_id(tile_id, alternative_index); + + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_id, alternative_id)); + int terrain_set = tile_data->get_terrain_set(); + if (terrain_set >= 0) { + ERR_FAIL_INDEX_V(terrain_set, get_terrain_sets_count(), Vector<Vector<Ref<Texture2D>>>()); + + LocalVector<int> bit_counts; + bit_counts.resize(get_terrains_count(terrain_set)); + for (int terrain = 0; terrain < get_terrains_count(terrain_set); terrain++) { + bit_counts[terrain] = 0; + } + for (int terrain_bit = 0; terrain_bit < TileSet::CELL_NEIGHBOR_MAX; terrain_bit++) { + TileSet::CellNeighbor cell_neighbor = TileSet::CellNeighbor(terrain_bit); + if (is_valid_peering_bit_terrain(terrain_set, cell_neighbor)) { + int terrain = tile_data->get_peering_bit_terrain(cell_neighbor); + if (terrain >= 0) { + bit_counts[terrain] += 1; + } + } + } + + for (int terrain = 0; terrain < get_terrains_count(terrain_set); terrain++) { + if ((bit_counts[terrain] > counts[terrain_set][terrain].count) || (bit_counts[terrain] == counts[terrain_set][terrain].count && tile_data->get_probability() > counts[terrain_set][terrain].probability)) { + counts[terrain_set][terrain].count = bit_counts[terrain]; + counts[terrain_set][terrain].probability = tile_data->get_probability(); + counts[terrain_set][terrain].texture = atlas_source->get_texture(); + counts[terrain_set][terrain].region = atlas_source->get_tile_texture_region(tile_id); + } + } + } + } + } } } - if (coords.size() == 0) { - return autotile_get_icon_coordinate(p_id); + // Generate the icons. + for (int terrain_set = 0; terrain_set < get_terrain_sets_count(); terrain_set++) { + for (int terrain = 0; terrain < get_terrains_count(terrain_set); terrain++) { + Ref<Image> image; + image.instantiate(); + if (counts[terrain_set][terrain].count > 0) { + // Get the best tile. + Ref<Texture2D> texture = counts[terrain_set][terrain].texture; + Rect2 region = counts[terrain_set][terrain].region; + image->create(region.size.x, region.size.y, false, Image::FORMAT_RGBA8); + image->blit_rect(texture->get_image(), region, Point2()); + image->resize(p_size.x, p_size.y, Image::INTERPOLATE_NEAREST); + } else { + image->create(1, 1, false, Image::FORMAT_RGBA8); + image->set_pixel(0, 0, get_terrain_color(terrain_set, terrain)); + } + Ref<ImageTexture> icon; + icon.instantiate(); + icon->create_from_image(image); + icon->set_size_override(p_size); + + output.write[terrain_set].write[terrain] = icon; + } + } + return output; +} + +void TileSet::_source_changed() { + emit_changed(); +} + +Vector<Point2> TileSet::_get_square_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { + Rect2 bit_rect; + bit_rect.size = Vector2(p_size) / 3; + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + bit_rect.position = Vector2(1, -1); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + bit_rect.position = Vector2(1, 1); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + bit_rect.position = Vector2(-1, 1); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + bit_rect.position = Vector2(-3, 1); + break; + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + bit_rect.position = Vector2(-3, -1); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + bit_rect.position = Vector2(-3, -3); + break; + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + bit_rect.position = Vector2(-1, -3); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + bit_rect.position = Vector2(1, -3); + break; + default: + break; + } + bit_rect.position *= Vector2(p_size) / 6.0; + Vector<Vector2> polygon; + polygon.push_back(bit_rect.position); + polygon.push_back(Vector2(bit_rect.get_end().x, bit_rect.position.y)); + polygon.push_back(bit_rect.get_end()); + polygon.push_back(Vector2(bit_rect.position.x, bit_rect.get_end().y)); + return polygon; +} + +Vector<Point2> TileSet::_get_square_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { + Vector2 unit = Vector2(p_size) / 6.0; + Vector<Vector2> polygon; + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + polygon.push_back(Vector2(0, 3) * unit); + polygon.push_back(Vector2(3, 3) * unit); + polygon.push_back(Vector2(3, 0) * unit); + polygon.push_back(Vector2(1, 0) * unit); + polygon.push_back(Vector2(1, 1) * unit); + polygon.push_back(Vector2(0, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + polygon.push_back(Vector2(0, 3) * unit); + polygon.push_back(Vector2(-3, 3) * unit); + polygon.push_back(Vector2(-3, 0) * unit); + polygon.push_back(Vector2(-1, 0) * unit); + polygon.push_back(Vector2(-1, 1) * unit); + polygon.push_back(Vector2(0, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + polygon.push_back(Vector2(0, -3) * unit); + polygon.push_back(Vector2(-3, -3) * unit); + polygon.push_back(Vector2(-3, 0) * unit); + polygon.push_back(Vector2(-1, 0) * unit); + polygon.push_back(Vector2(-1, -1) * unit); + polygon.push_back(Vector2(0, -1) * unit); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + polygon.push_back(Vector2(0, -3) * unit); + polygon.push_back(Vector2(3, -3) * unit); + polygon.push_back(Vector2(3, 0) * unit); + polygon.push_back(Vector2(1, 0) * unit); + polygon.push_back(Vector2(1, -1) * unit); + polygon.push_back(Vector2(0, -1) * unit); + break; + default: + break; + } + return polygon; +} + +Vector<Point2> TileSet::_get_square_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { + Vector2 unit = Vector2(p_size) / 6.0; + Vector<Vector2> polygon; + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + polygon.push_back(Vector2(1, -1) * unit); + polygon.push_back(Vector2(3, -3) * unit); + polygon.push_back(Vector2(3, 3) * unit); + polygon.push_back(Vector2(1, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + polygon.push_back(Vector2(-1, 1) * unit); + polygon.push_back(Vector2(-3, 3) * unit); + polygon.push_back(Vector2(3, 3) * unit); + polygon.push_back(Vector2(1, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + polygon.push_back(Vector2(-1, -1) * unit); + polygon.push_back(Vector2(-3, -3) * unit); + polygon.push_back(Vector2(-3, 3) * unit); + polygon.push_back(Vector2(-1, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + polygon.push_back(Vector2(-1, -1) * unit); + polygon.push_back(Vector2(-3, -3) * unit); + polygon.push_back(Vector2(3, -3) * unit); + polygon.push_back(Vector2(1, -1) * unit); + break; + default: + break; + } + return polygon; +} + +Vector<Point2> TileSet::_get_isometric_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { + Vector2 unit = Vector2(p_size) / 6.0; + Vector<Vector2> polygon; + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: + polygon.push_back(Vector2(1, 0) * unit); + polygon.push_back(Vector2(2, -1) * unit); + polygon.push_back(Vector2(3, 0) * unit); + polygon.push_back(Vector2(2, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + polygon.push_back(Vector2(0, 1) * unit); + polygon.push_back(Vector2(1, 2) * unit); + polygon.push_back(Vector2(2, 1) * unit); + polygon.push_back(Vector2(1, 0) * unit); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: + polygon.push_back(Vector2(0, 1) * unit); + polygon.push_back(Vector2(-1, 2) * unit); + polygon.push_back(Vector2(0, 3) * unit); + polygon.push_back(Vector2(1, 2) * unit); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + polygon.push_back(Vector2(0, 1) * unit); + polygon.push_back(Vector2(-1, 2) * unit); + polygon.push_back(Vector2(-2, 1) * unit); + polygon.push_back(Vector2(-1, 0) * unit); + break; + case TileSet::CELL_NEIGHBOR_LEFT_CORNER: + polygon.push_back(Vector2(-1, 0) * unit); + polygon.push_back(Vector2(-2, -1) * unit); + polygon.push_back(Vector2(-3, 0) * unit); + polygon.push_back(Vector2(-2, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + polygon.push_back(Vector2(0, -1) * unit); + polygon.push_back(Vector2(-1, -2) * unit); + polygon.push_back(Vector2(-2, -1) * unit); + polygon.push_back(Vector2(-1, 0) * unit); + break; + case TileSet::CELL_NEIGHBOR_TOP_CORNER: + polygon.push_back(Vector2(0, -1) * unit); + polygon.push_back(Vector2(-1, -2) * unit); + polygon.push_back(Vector2(0, -3) * unit); + polygon.push_back(Vector2(1, -2) * unit); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + polygon.push_back(Vector2(0, -1) * unit); + polygon.push_back(Vector2(1, -2) * unit); + polygon.push_back(Vector2(2, -1) * unit); + polygon.push_back(Vector2(1, 0) * unit); + break; + default: + break; + } + return polygon; +} + +Vector<Point2> TileSet::_get_isometric_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { + Vector2 unit = Vector2(p_size) / 6.0; + Vector<Vector2> polygon; + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: + polygon.push_back(Vector2(0.5, -0.5) * unit); + polygon.push_back(Vector2(1.5, -1.5) * unit); + polygon.push_back(Vector2(3, 0) * unit); + polygon.push_back(Vector2(1.5, 1.5) * unit); + polygon.push_back(Vector2(0.5, 0.5) * unit); + polygon.push_back(Vector2(1, 0) * unit); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: + polygon.push_back(Vector2(-0.5, 0.5) * unit); + polygon.push_back(Vector2(-1.5, 1.5) * unit); + polygon.push_back(Vector2(0, 3) * unit); + polygon.push_back(Vector2(1.5, 1.5) * unit); + polygon.push_back(Vector2(0.5, 0.5) * unit); + polygon.push_back(Vector2(0, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_LEFT_CORNER: + polygon.push_back(Vector2(-0.5, -0.5) * unit); + polygon.push_back(Vector2(-1.5, -1.5) * unit); + polygon.push_back(Vector2(-3, 0) * unit); + polygon.push_back(Vector2(-1.5, 1.5) * unit); + polygon.push_back(Vector2(-0.5, 0.5) * unit); + polygon.push_back(Vector2(-1, 0) * unit); + break; + case TileSet::CELL_NEIGHBOR_TOP_CORNER: + polygon.push_back(Vector2(-0.5, -0.5) * unit); + polygon.push_back(Vector2(-1.5, -1.5) * unit); + polygon.push_back(Vector2(0, -3) * unit); + polygon.push_back(Vector2(1.5, -1.5) * unit); + polygon.push_back(Vector2(0.5, -0.5) * unit); + polygon.push_back(Vector2(0, -1) * unit); + break; + default: + break; + } + return polygon; +} + +Vector<Point2> TileSet::_get_isometric_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { + Vector2 unit = Vector2(p_size) / 6.0; + Vector<Vector2> polygon; + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + polygon.push_back(Vector2(1, 0) * unit); + polygon.push_back(Vector2(3, 0) * unit); + polygon.push_back(Vector2(0, 3) * unit); + polygon.push_back(Vector2(0, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + polygon.push_back(Vector2(-1, 0) * unit); + polygon.push_back(Vector2(-3, 0) * unit); + polygon.push_back(Vector2(0, 3) * unit); + polygon.push_back(Vector2(0, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + polygon.push_back(Vector2(-1, 0) * unit); + polygon.push_back(Vector2(-3, 0) * unit); + polygon.push_back(Vector2(0, -3) * unit); + polygon.push_back(Vector2(0, -1) * unit); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + polygon.push_back(Vector2(1, 0) * unit); + polygon.push_back(Vector2(3, 0) * unit); + polygon.push_back(Vector2(0, -3) * unit); + polygon.push_back(Vector2(0, -1) * unit); + break; + default: + break; + } + return polygon; +} + +Vector<Point2> TileSet::_get_half_offset_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { + Vector<Vector2> point_list; + point_list.push_back(Vector2(3, (3.0 * (1.0 - p_overlap * 2.0)) / 2.0)); + point_list.push_back(Vector2(3, 3.0 * (1.0 - p_overlap * 2.0))); + point_list.push_back(Vector2(2, 3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0))); + point_list.push_back(Vector2(1, 3.0 - p_overlap * 2.0)); + point_list.push_back(Vector2(0, 3)); + point_list.push_back(Vector2(-1, 3.0 - p_overlap * 2.0)); + point_list.push_back(Vector2(-2, 3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0))); + point_list.push_back(Vector2(-3, 3.0 * (1.0 - p_overlap * 2.0))); + point_list.push_back(Vector2(-3, (3.0 * (1.0 - p_overlap * 2.0)) / 2.0)); + point_list.push_back(Vector2(-3, -(3.0 * (1.0 - p_overlap * 2.0)) / 2.0)); + point_list.push_back(Vector2(-3, -3.0 * (1.0 - p_overlap * 2.0))); + point_list.push_back(Vector2(-2, -3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0))); + point_list.push_back(Vector2(-1, -(3.0 - p_overlap * 2.0))); + point_list.push_back(Vector2(0, -3)); + point_list.push_back(Vector2(1, -(3.0 - p_overlap * 2.0))); + point_list.push_back(Vector2(2, -3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0))); + point_list.push_back(Vector2(3, -3.0 * (1.0 - p_overlap * 2.0))); + point_list.push_back(Vector2(3, -(3.0 * (1.0 - p_overlap * 2.0)) / 2.0)); + + Vector2 unit = Vector2(p_size) / 6.0; + for (int i = 0; i < point_list.size(); i++) { + point_list.write[i] = point_list[i] * unit; + } + + Vector<Vector2> polygon; + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + polygon.push_back(point_list[17]); + polygon.push_back(point_list[0]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + polygon.push_back(point_list[0]); + polygon.push_back(point_list[1]); + polygon.push_back(point_list[2]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + polygon.push_back(point_list[2]); + polygon.push_back(point_list[3]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: + polygon.push_back(point_list[3]); + polygon.push_back(point_list[4]); + polygon.push_back(point_list[5]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + polygon.push_back(point_list[5]); + polygon.push_back(point_list[6]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + polygon.push_back(point_list[6]); + polygon.push_back(point_list[7]); + polygon.push_back(point_list[8]); + break; + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + polygon.push_back(point_list[8]); + polygon.push_back(point_list[9]); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + polygon.push_back(point_list[9]); + polygon.push_back(point_list[10]); + polygon.push_back(point_list[11]); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + polygon.push_back(point_list[11]); + polygon.push_back(point_list[12]); + break; + case TileSet::CELL_NEIGHBOR_TOP_CORNER: + polygon.push_back(point_list[12]); + polygon.push_back(point_list[13]); + polygon.push_back(point_list[14]); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + polygon.push_back(point_list[14]); + polygon.push_back(point_list[15]); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + polygon.push_back(point_list[15]); + polygon.push_back(point_list[16]); + polygon.push_back(point_list[17]); + break; + default: + break; + } } else { - uint32_t picked_value = Math::rand() % priority_sum; - uint32_t upper_bound; - uint32_t lower_bound = 0; - Vector2 result = coords.front()->get(); - List<Vector2>::Element *coords_E = coords.front(); - List<uint32_t>::Element *priorities_E = priorities.front(); - while (priorities_E) { - upper_bound = lower_bound + priorities_E->get(); - if (lower_bound <= picked_value && picked_value < upper_bound) { - result = coords_E->get(); + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { + for (int i = 0; i < point_list.size(); i++) { + point_list.write[i] = Vector2(point_list[i].y, point_list[i].x); + } + } + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: + polygon.push_back(point_list[3]); + polygon.push_back(point_list[4]); + polygon.push_back(point_list[5]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + polygon.push_back(point_list[2]); + polygon.push_back(point_list[3]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + polygon.push_back(point_list[0]); + polygon.push_back(point_list[1]); + polygon.push_back(point_list[2]); break; + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + polygon.push_back(point_list[17]); + polygon.push_back(point_list[0]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + polygon.push_back(point_list[15]); + polygon.push_back(point_list[16]); + polygon.push_back(point_list[17]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + polygon.push_back(point_list[14]); + polygon.push_back(point_list[15]); + break; + case TileSet::CELL_NEIGHBOR_LEFT_CORNER: + polygon.push_back(point_list[12]); + polygon.push_back(point_list[13]); + polygon.push_back(point_list[14]); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + polygon.push_back(point_list[11]); + polygon.push_back(point_list[12]); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + polygon.push_back(point_list[9]); + polygon.push_back(point_list[10]); + polygon.push_back(point_list[11]); + break; + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + polygon.push_back(point_list[8]); + polygon.push_back(point_list[9]); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + polygon.push_back(point_list[6]); + polygon.push_back(point_list[7]); + polygon.push_back(point_list[8]); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + polygon.push_back(point_list[5]); + polygon.push_back(point_list[6]); + break; + default: + break; + } + } + + int half_polygon_size = polygon.size(); + for (int i = 0; i < half_polygon_size; i++) { + polygon.push_back(polygon[half_polygon_size - 1 - i] / 3.0); + } + + return polygon; +} + +Vector<Point2> TileSet::_get_half_offset_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { + Vector<Vector2> point_list; + point_list.push_back(Vector2(3, 0)); + point_list.push_back(Vector2(3, 3.0 * (1.0 - p_overlap * 2.0))); + point_list.push_back(Vector2(1.5, (3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0)); + point_list.push_back(Vector2(0, 3)); + point_list.push_back(Vector2(-1.5, (3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0)); + point_list.push_back(Vector2(-3, 3.0 * (1.0 - p_overlap * 2.0))); + point_list.push_back(Vector2(-3, 0)); + point_list.push_back(Vector2(-3, -3.0 * (1.0 - p_overlap * 2.0))); + point_list.push_back(Vector2(-1.5, -(3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0)); + point_list.push_back(Vector2(0, -3)); + point_list.push_back(Vector2(1.5, -(3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0)); + point_list.push_back(Vector2(3, -3.0 * (1.0 - p_overlap * 2.0))); + + Vector2 unit = Vector2(p_size) / 6.0; + for (int i = 0; i < point_list.size(); i++) { + point_list.write[i] = point_list[i] * unit; + } + + Vector<Vector2> polygon; + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + polygon.push_back(point_list[0]); + polygon.push_back(point_list[1]); + polygon.push_back(point_list[2]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: + polygon.push_back(point_list[2]); + polygon.push_back(point_list[3]); + polygon.push_back(point_list[4]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + polygon.push_back(point_list[4]); + polygon.push_back(point_list[5]); + polygon.push_back(point_list[6]); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + polygon.push_back(point_list[6]); + polygon.push_back(point_list[7]); + polygon.push_back(point_list[8]); + break; + case TileSet::CELL_NEIGHBOR_TOP_CORNER: + polygon.push_back(point_list[8]); + polygon.push_back(point_list[9]); + polygon.push_back(point_list[10]); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + polygon.push_back(point_list[10]); + polygon.push_back(point_list[11]); + polygon.push_back(point_list[0]); + break; + default: + break; + } + } else { + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { + for (int i = 0; i < point_list.size(); i++) { + point_list.write[i] = Vector2(point_list[i].y, point_list[i].x); } - lower_bound = upper_bound; - priorities_E = priorities_E->next(); - coords_E = coords_E->next(); } + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: + polygon.push_back(point_list[2]); + polygon.push_back(point_list[3]); + polygon.push_back(point_list[4]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + polygon.push_back(point_list[0]); + polygon.push_back(point_list[1]); + polygon.push_back(point_list[2]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + polygon.push_back(point_list[10]); + polygon.push_back(point_list[11]); + polygon.push_back(point_list[0]); + break; + case TileSet::CELL_NEIGHBOR_LEFT_CORNER: + polygon.push_back(point_list[8]); + polygon.push_back(point_list[9]); + polygon.push_back(point_list[10]); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + polygon.push_back(point_list[6]); + polygon.push_back(point_list[7]); + polygon.push_back(point_list[8]); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + polygon.push_back(point_list[4]); + polygon.push_back(point_list[5]); + polygon.push_back(point_list[6]); + break; + default: + break; + } + } - return result; + int half_polygon_size = polygon.size(); + for (int i = 0; i < half_polygon_size; i++) { + polygon.push_back(polygon[half_polygon_size - 1 - i] / 3.0); } + + return polygon; } -Vector2 TileSet::atlastile_get_subtile_by_priority(int p_id, const Node *p_tilemap_node, const Vector2 &p_tile_location) { - ERR_FAIL_COND_V(!tile_map.has(p_id), Vector2()); - //First try to forward selection to script - if (get_script_instance() != nullptr) { - if (get_script_instance()->has_method("_forward_atlas_subtile_selection")) { - Variant ret = get_script_instance()->call("_forward_atlas_subtile_selection", p_id, p_tilemap_node, p_tile_location); - if (ret.get_type() == Variant::VECTOR2) { - return ret; +Vector<Point2> TileSet::_get_half_offset_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { + Vector<Vector2> point_list; + point_list.push_back(Vector2(3, 3.0 * (1.0 - p_overlap * 2.0))); + point_list.push_back(Vector2(0, 3)); + point_list.push_back(Vector2(-3, 3.0 * (1.0 - p_overlap * 2.0))); + point_list.push_back(Vector2(-3, -3.0 * (1.0 - p_overlap * 2.0))); + point_list.push_back(Vector2(0, -3)); + point_list.push_back(Vector2(3, -3.0 * (1.0 - p_overlap * 2.0))); + + Vector2 unit = Vector2(p_size) / 6.0; + for (int i = 0; i < point_list.size(); i++) { + point_list.write[i] = point_list[i] * unit; + } + + Vector<Vector2> polygon; + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + polygon.push_back(point_list[5]); + polygon.push_back(point_list[0]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + polygon.push_back(point_list[0]); + polygon.push_back(point_list[1]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + polygon.push_back(point_list[1]); + polygon.push_back(point_list[2]); + break; + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + polygon.push_back(point_list[2]); + polygon.push_back(point_list[3]); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + polygon.push_back(point_list[3]); + polygon.push_back(point_list[4]); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + polygon.push_back(point_list[4]); + polygon.push_back(point_list[5]); + break; + default: + break; + } + } else { + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { + for (int i = 0; i < point_list.size(); i++) { + point_list.write[i] = Vector2(point_list[i].y, point_list[i].x); } } + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + polygon.push_back(point_list[0]); + polygon.push_back(point_list[1]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + polygon.push_back(point_list[5]); + polygon.push_back(point_list[0]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + polygon.push_back(point_list[4]); + polygon.push_back(point_list[5]); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + polygon.push_back(point_list[3]); + polygon.push_back(point_list[4]); + break; + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + polygon.push_back(point_list[2]); + polygon.push_back(point_list[3]); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + polygon.push_back(point_list[1]); + polygon.push_back(point_list[2]); + break; + default: + break; + } + } + + int half_polygon_size = polygon.size(); + for (int i = 0; i < half_polygon_size; i++) { + polygon.push_back(polygon[half_polygon_size - 1 - i] / 3.0); } - Vector2 coord = tile_get_region(p_id).size / autotile_get_size(p_id); + return polygon; +} + +void TileSet::reset_state() { + occlusion_layers.clear(); + physics_layers.clear(); + custom_data_layers.clear(); +} + +const Vector2i TileSetSource::INVALID_ATLAS_COORDS = Vector2i(-1, -1); +const int TileSetSource::INVALID_TILE_ALTERNATIVE = -1; + +#ifndef DISABLE_DEPRECATED +void TileSet::compatibility_conversion() { + for (Map<int, CompatibilityTileData *>::Element *E = compatibility_data.front(); E; E = E->next()) { + CompatibilityTileData *ctd = E->value(); - List<Vector2> coords; - for (int x = 0; x < coord.x; x++) { - for (int y = 0; y < coord.y; y++) { - for (int i = 0; i < autotile_get_subtile_priority(p_id, Vector2(x, y)); i++) { - coords.push_back(Vector2(x, y)); + // Add the texture + TileSetAtlasSource *atlas_source = memnew(TileSetAtlasSource); + int source_id = add_source(Ref<TileSetSource>(atlas_source)); + + atlas_source->set_texture(ctd->texture); + + // Handle each tile as a new source. Not optimal but at least it should stay compatible. + switch (ctd->tile_mode) { + case 0: // SINGLE_TILE + // TODO + break; + case 1: // AUTO_TILE + // TODO + break; + case 2: // ATLAS_TILE + atlas_source->set_margins(ctd->region.get_position()); + atlas_source->set_separation(Vector2i(ctd->autotile_spacing, ctd->autotile_spacing)); + atlas_source->set_texture_region_size(ctd->autotile_tile_size); + + Size2i atlas_size = ctd->region.get_size() / (ctd->autotile_tile_size + atlas_source->get_separation()); + for (int i = 0; i < atlas_size.x; i++) { + for (int j = 0; j < atlas_size.y; j++) { + Vector2i coords = Vector2i(i, j); + + for (int flags = 0; flags < 8; flags++) { + bool flip_h = flags & 1; + bool flip_v = flags & 2; + bool transpose = flags & 4; + + int alternative_tile = 0; + if (!atlas_source->has_tile(coords)) { + atlas_source->create_tile(coords); + } else { + alternative_tile = atlas_source->create_alternative_tile(coords); + } + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(coords, alternative_tile)); + + tile_data->set_flip_h(flip_h); + tile_data->set_flip_v(flip_v); + tile_data->set_transpose(transpose); + tile_data->tile_set_material(ctd->material); + tile_data->set_modulate(ctd->modulate); + tile_data->set_z_index(ctd->z_index); + if (ctd->autotile_occluder_map.has(coords)) { + if (get_occlusion_layers_count() < 1) { + set_occlusion_layers_count(1); + } + tile_data->set_occluder(0, ctd->autotile_occluder_map[coords]); + } + if (ctd->autotile_navpoly_map.has(coords)) { + if (get_navigation_layers_count() < 1) { + set_navigation_layers_count(1); + } + tile_data->set_navigation_polygon(0, ctd->autotile_navpoly_map[coords]); + } + if (ctd->autotile_priority_map.has(coords)) { + tile_data->set_probability(ctd->autotile_priority_map[coords]); + } + if (ctd->autotile_z_index_map.has(coords)) { + tile_data->set_z_index(ctd->autotile_z_index_map[coords]); + } + + // Add the shapes. + if (ctd->shapes.size() > 0) { + if (get_physics_layers_count() < 1) { + set_physics_layers_count(1); + } + } + for (int k = 0; k < ctd->shapes.size(); k++) { + CompatibilityShapeData csd = ctd->shapes[k]; + if (csd.autotile_coords == coords) { + Ref<ConvexPolygonShape2D> convex_shape = csd.shape; // Only ConvexPolygonShape2D are supported, which is the default type used by the 3.x editor + if (convex_shape.is_valid()) { + Vector<Vector2> polygon = convex_shape->get_points(); + for (int point_index = 0; point_index < polygon.size(); point_index++) { + polygon.write[point_index] = csd.transform.xform(polygon[point_index]); + } + tile_data->set_collision_polygons_count(0, tile_data->get_collision_polygons_count(0) + 1); + int index = tile_data->get_collision_polygons_count(0) - 1; + tile_data->set_collision_polygon_one_way(0, index, csd.one_way); + tile_data->set_collision_polygon_one_way_margin(0, index, csd.one_way_margin); + tile_data->set_collision_polygon_points(0, index, polygon); + } + } + } + + // -- TODO: handle -- + // Those are offset for the whole atlas, they are likely useless for the atlases, but might make sense for single tiles. + // texture offset + // occluder_offset + // navigation_offset + + // For terrains, ignored for now? + // bitmask_mode + // bitmask_flags + } + } + } + break; + } + + // Offset all shapes + for (int k = 0; k < ctd->shapes.size(); k++) { + Ref<ConvexPolygonShape2D> convex = ctd->shapes[k].shape; + if (convex.is_valid()) { + Vector<Vector2> points = convex->get_points(); + for (int i_point = 0; i_point < points.size(); i_point++) { + points.write[i_point] = points[i_point] - get_tile_size() / 2; + } + convex->set_points(points); } } + + // Add the mapping to the map + compatibility_source_mapping.insert(E->key(), source_id); + } + + // Reset compatibility data + for (Map<int, CompatibilityTileData *>::Element *E = compatibility_data.front(); E; E = E->next()) { + memdelete(E->get()); } - if (coords.size() == 0) { - return autotile_get_icon_coordinate(p_id); + compatibility_data = Map<int, CompatibilityTileData *>(); +} +#endif // DISABLE_DEPRECATED + +bool TileSet::_set(const StringName &p_name, const Variant &p_value) { + Vector<String> components = String(p_name).split("/", true, 2); + +#ifndef DISABLE_DEPRECATED + // TODO: THIS IS HOW WE CHECK IF WE HAVE A DEPRECATED RESOURCE + // This should be moved to a dedicated conversion system + if (components.size() >= 1 && components[0].is_valid_int()) { + int id = components[0].to_int(); + + // Get or create the compatibility object + CompatibilityTileData *ctd; + Map<int, CompatibilityTileData *>::Element *E = compatibility_data.find(id); + if (!E) { + ctd = memnew(CompatibilityTileData); + compatibility_data.insert(id, ctd); + } else { + ctd = E->get(); + } + + if (components.size() < 2) { + return false; + } + + String what = components[1]; + + if (what == "name") { + ctd->name = p_value; + } else if (what == "texture") { + ctd->texture = p_value; + } else if (what == "tex_offset") { + ctd->tex_offset = p_value; + } else if (what == "material") { + ctd->material = p_value; + } else if (what == "modulate") { + ctd->modulate = p_value; + } else if (what == "region") { + ctd->region = p_value; + } else if (what == "tile_mode") { + ctd->tile_mode = p_value; + } else if (what.left(9) == "autotile") { + what = what.substr(9); + if (what == "bitmask_mode") { + ctd->autotile_bitmask_mode = p_value; + } else if (what == "icon_coordinate") { + ctd->autotile_icon_coordinate = p_value; + } else if (what == "tile_size") { + ctd->autotile_tile_size = p_value; + } else if (what == "spacing") { + ctd->autotile_spacing = p_value; + } else if (what == "bitmask_flags") { + if (p_value.is_array()) { + Array p = p_value; + Vector2i last_coord; + while (p.size() > 0) { + if (p[0].get_type() == Variant::VECTOR2) { + last_coord = p[0]; + } else if (p[0].get_type() == Variant::INT) { + ctd->autotile_bitmask_flags.insert(last_coord, p[0]); + } + p.pop_front(); + } + } + } else if (what == "occluder_map") { + Array p = p_value; + Vector2 last_coord; + while (p.size() > 0) { + if (p[0].get_type() == Variant::VECTOR2) { + last_coord = p[0]; + } else if (p[0].get_type() == Variant::OBJECT) { + ctd->autotile_occluder_map.insert(last_coord, p[0]); + } + p.pop_front(); + } + } else if (what == "navpoly_map") { + Array p = p_value; + Vector2 last_coord; + while (p.size() > 0) { + if (p[0].get_type() == Variant::VECTOR2) { + last_coord = p[0]; + } else if (p[0].get_type() == Variant::OBJECT) { + ctd->autotile_navpoly_map.insert(last_coord, p[0]); + } + p.pop_front(); + } + } else if (what == "priority_map") { + Array p = p_value; + Vector3 val; + Vector2 v; + int priority; + while (p.size() > 0) { + val = p[0]; + if (val.z > 1) { + v.x = val.x; + v.y = val.y; + priority = (int)val.z; + ctd->autotile_priority_map.insert(v, priority); + } + p.pop_front(); + } + } else if (what == "z_index_map") { + Array p = p_value; + Vector3 val; + Vector2 v; + int z_index; + while (p.size() > 0) { + val = p[0]; + if (val.z != 0) { + v.x = val.x; + v.y = val.y; + z_index = (int)val.z; + ctd->autotile_z_index_map.insert(v, z_index); + } + p.pop_front(); + } + } + + } else if (what == "shapes") { + Array p = p_value; + for (int i = 0; i < p.size(); i++) { + CompatibilityShapeData csd; + Dictionary d = p[i]; + for (int j = 0; j < d.size(); j++) { + String key = d.get_key_at_index(j); + if (key == "autotile_coord") { + csd.autotile_coords = d[key]; + } else if (key == "one_way") { + csd.one_way = d[key]; + } else if (key == "one_way_margin") { + csd.one_way_margin = d[key]; + } else if (key == "shape") { + csd.shape = d[key]; + } else if (key == "shape_transform") { + csd.transform = d[key]; + } + } + ctd->shapes.push_back(csd); + } + + /* + // IGNORED FOR NOW, they seem duplicated data compared to the shapes array + } else if (what == "shape") { + // TODO + } else if (what == "shape_offset") { + // TODO + } else if (what == "shape_transform") { + // TODO + } else if (what == "shape_one_way") { + // TODO + } else if (what == "shape_one_way_margin") { + // TODO + } + // IGNORED FOR NOW, maybe useless ? + else if (what == "occluder_offset") { + // Not + } else if (what == "navigation_offset") { + // TODO + } + */ + + } else if (what == "z_index") { + ctd->z_index = p_value; + + // TODO: remove the conversion from here, it's not where it should be done + compatibility_conversion(); + } else { + return false; + } } else { - return coords[Math::random(0, (int)coords.size())]; +#endif // DISABLE_DEPRECATED + + // This is now a new property. + if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { + // Occlusion layers. + int index = components[0].trim_prefix("occlusion_layer_").to_int(); + ERR_FAIL_COND_V(index < 0, false); + if (components[1] == "light_mask") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); + if (index >= occlusion_layers.size()) { + set_occlusion_layers_count(index + 1); + } + set_occlusion_layer_light_mask(index, p_value); + return true; + } else if (components[1] == "sdf_collision") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::BOOL, false); + if (index >= occlusion_layers.size()) { + set_occlusion_layers_count(index + 1); + } + set_occlusion_layer_sdf_collision(index, p_value); + return true; + } + } else if (components.size() == 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { + // Physics layers. + int index = components[0].trim_prefix("physics_layer_").to_int(); + ERR_FAIL_COND_V(index < 0, false); + if (components[1] == "collision_layer") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); + if (index >= physics_layers.size()) { + set_physics_layers_count(index + 1); + } + set_physics_layer_collision_layer(index, p_value); + return true; + } else if (components[1] == "collision_mask") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); + if (index >= physics_layers.size()) { + set_physics_layers_count(index + 1); + } + set_physics_layer_collision_mask(index, p_value); + return true; + } else if (components[1] == "physics_material") { + Ref<PhysicsMaterial> physics_material = p_value; + ERR_FAIL_COND_V(!physics_material.is_valid(), false); + if (index >= physics_layers.size()) { + set_physics_layers_count(index + 1); + } + set_physics_layer_physics_material(index, physics_material); + return true; + } + } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int()) { + // Terrains. + int terrain_set_index = components[0].trim_prefix("terrain_set_").to_int(); + ERR_FAIL_COND_V(terrain_set_index < 0, false); + if (components[1] == "mode") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); + if (terrain_set_index >= terrain_sets.size()) { + set_terrain_sets_count(terrain_set_index + 1); + } + set_terrain_set_mode(terrain_set_index, TerrainMode(int(p_value))); + } else if (components[1] == "terrains_count") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); + if (terrain_set_index >= terrain_sets.size()) { + set_terrain_sets_count(terrain_set_index + 1); + } + set_terrains_count(terrain_set_index, p_value); + return true; + } else if (components.size() >= 3 && components[1].begins_with("terrain_") && components[1].trim_prefix("terrain_").is_valid_int()) { + int terrain_index = components[1].trim_prefix("terrain_").to_int(); + ERR_FAIL_COND_V(terrain_index < 0, false); + if (components[2] == "name") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false); + if (terrain_set_index >= terrain_sets.size()) { + set_terrain_sets_count(terrain_set_index + 1); + } + if (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { + set_terrains_count(terrain_set_index, terrain_index + 1); + } + set_terrain_name(terrain_set_index, terrain_index, p_value); + return true; + } else if (components[2] == "color") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::COLOR, false); + if (terrain_set_index >= terrain_sets.size()) { + set_terrain_sets_count(terrain_set_index + 1); + } + if (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { + set_terrains_count(terrain_set_index, terrain_index + 1); + } + set_terrain_color(terrain_set_index, terrain_index, p_value); + return true; + } + } + } else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_int()) { + // Navigation layers. + int index = components[0].trim_prefix("navigation_layer_").to_int(); + ERR_FAIL_COND_V(index < 0, false); + if (components[1] == "layers") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); + if (index >= navigation_layers.size()) { + set_navigation_layers_count(index + 1); + } + set_navigation_layer_layers(index, p_value); + return true; + } + } else if (components.size() == 2 && components[0].begins_with("custom_data_layer_") && components[0].trim_prefix("custom_data_layer_").is_valid_int()) { + // Custom data layers. + int index = components[0].trim_prefix("custom_data_layer_").to_int(); + ERR_FAIL_COND_V(index < 0, false); + if (components[1] == "name") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false); + if (index >= custom_data_layers.size()) { + set_custom_data_layers_count(index + 1); + } + set_custom_data_name(index, p_value); + return true; + } else if (components[1] == "type") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); + if (index >= custom_data_layers.size()) { + set_custom_data_layers_count(index + 1); + } + set_custom_data_type(index, Variant::Type(int(p_value))); + return true; + } + } else if (components.size() == 2 && components[0] == "sources" && components[1].is_valid_int()) { + // Create source only if it does not exists. + int source_id = components[1].to_int(); + + if (!has_source(source_id)) { + add_source(p_value, source_id); + } + return true; + } + +#ifndef DISABLE_DEPRECATED } +#endif // DISABLE_DEPRECATED + + return false; } -void TileSet::tile_set_name(int p_id, const String &p_name) { - ERR_FAIL_COND(!tile_map.has(p_id)); - tile_map[p_id].name = p_name; - emit_changed(); +bool TileSet::_get(const StringName &p_name, Variant &r_ret) const { + Vector<String> components = String(p_name).split("/", true, 2); + + if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { + // Occlusion layers. + int index = components[0].trim_prefix("occlusion_layer_").to_int(); + if (index < 0 || index >= occlusion_layers.size()) { + return false; + } + if (components[1] == "light_mask") { + r_ret = get_occlusion_layer_light_mask(index); + return true; + } else if (components[1] == "sdf_collision") { + r_ret = get_occlusion_layer_sdf_collision(index); + return true; + } + } else if (components.size() == 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { + // Physics layers. + int index = components[0].trim_prefix("physics_layer_").to_int(); + if (index < 0 || index >= physics_layers.size()) { + return false; + } + if (components[1] == "collision_layer") { + r_ret = get_physics_layer_collision_layer(index); + return true; + } else if (components[1] == "collision_mask") { + r_ret = get_physics_layer_collision_mask(index); + return true; + } else if (components[1] == "physics_material") { + r_ret = get_physics_layer_physics_material(index); + return true; + } + } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int()) { + // Terrains. + int terrain_set_index = components[0].trim_prefix("terrain_set_").to_int(); + if (terrain_set_index < 0 || terrain_set_index >= terrain_sets.size()) { + return false; + } + if (components[1] == "mode") { + r_ret = get_terrain_set_mode(terrain_set_index); + return true; + } else if (components[1] == "terrains_count") { + r_ret = get_terrains_count(terrain_set_index); + return true; + } else if (components.size() >= 3 && components[1].begins_with("terrain_") && components[1].trim_prefix("terrain_").is_valid_int()) { + int terrain_index = components[1].trim_prefix("terrain_").to_int(); + if (terrain_index < 0 || terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { + return false; + } + if (components[2] == "name") { + r_ret = get_terrain_name(terrain_set_index, terrain_index); + return true; + } else if (components[2] == "color") { + r_ret = get_terrain_color(terrain_set_index, terrain_index); + return true; + } + } + } else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_int()) { + // navigation layers. + int index = components[0].trim_prefix("navigation_layer_").to_int(); + if (index < 0 || index >= navigation_layers.size()) { + return false; + } + if (components[1] == "layers") { + r_ret = get_navigation_layer_layers(index); + return true; + } + } else if (components.size() == 2 && components[0].begins_with("custom_data_layer_") && components[0].trim_prefix("custom_data_layer_").is_valid_int()) { + // Custom data layers. + int index = components[0].trim_prefix("custom_data_layer_").to_int(); + if (index < 0 || index >= custom_data_layers.size()) { + return false; + } + if (components[1] == "name") { + r_ret = get_custom_data_name(index); + return true; + } else if (components[1] == "type") { + r_ret = get_custom_data_type(index); + return true; + } + } else if (components.size() == 2 && components[0] == "sources" && components[1].is_valid_int()) { + // Atlases data. + int source_id = components[1].to_int(); + + if (has_source(source_id)) { + r_ret = get_source(source_id); + return true; + } else { + return false; + } + } + + return false; } -String TileSet::tile_get_name(int p_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), String()); - return tile_map[p_id].name; +void TileSet::_get_property_list(List<PropertyInfo> *p_list) const { + PropertyInfo property_info; + // Rendering. + p_list->push_back(PropertyInfo(Variant::NIL, "Rendering", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < occlusion_layers.size(); i++) { + p_list->push_back(PropertyInfo(Variant::INT, vformat("occlusion_layer_%d/light_mask", i), PROPERTY_HINT_LAYERS_2D_RENDER)); + + // occlusion_layer_%d/sdf_collision + property_info = PropertyInfo(Variant::BOOL, vformat("occlusion_layer_%d/sdf_collision", i)); + if (occlusion_layers[i].sdf_collision == false) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + } + + // Physics. + p_list->push_back(PropertyInfo(Variant::NIL, "Physics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < physics_layers.size(); i++) { + p_list->push_back(PropertyInfo(Variant::INT, vformat("physics_layer_%d/collision_layer", i), PROPERTY_HINT_LAYERS_2D_PHYSICS)); + + // physics_layer_%d/collision_mask + property_info = PropertyInfo(Variant::INT, vformat("physics_layer_%d/collision_mask", i), PROPERTY_HINT_LAYERS_2D_PHYSICS); + if (physics_layers[i].collision_mask == 1) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + + // physics_layer_%d/physics_material + property_info = PropertyInfo(Variant::OBJECT, vformat("physics_layer_%d/physics_material", i), PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"); + if (!physics_layers[i].physics_material.is_valid()) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + } + + // Terrains. + p_list->push_back(PropertyInfo(Variant::NIL, "Terrains", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int terrain_set_index = 0; terrain_set_index < terrain_sets.size(); terrain_set_index++) { + p_list->push_back(PropertyInfo(Variant::INT, vformat("terrain_set_%d/mode", terrain_set_index), PROPERTY_HINT_ENUM, "Match corners and sides,Match corners,Match sides")); + p_list->push_back(PropertyInfo(Variant::INT, vformat("terrain_set_%d/terrains_count", terrain_set_index), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); + for (int terrain_index = 0; terrain_index < terrain_sets[terrain_set_index].terrains.size(); terrain_index++) { + p_list->push_back(PropertyInfo(Variant::STRING, vformat("terrain_set_%d/terrain_%d/name", terrain_set_index, terrain_index))); + p_list->push_back(PropertyInfo(Variant::COLOR, vformat("terrain_set_%d/terrain_%d/color", terrain_set_index, terrain_index))); + } + } + + // Navigation. + p_list->push_back(PropertyInfo(Variant::NIL, "Navigation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < navigation_layers.size(); i++) { + p_list->push_back(PropertyInfo(Variant::INT, vformat("navigation_layer_%d/layers", i), PROPERTY_HINT_LAYERS_2D_NAVIGATION)); + } + + // Custom data. + String argt = "Any"; + for (int i = 1; i < Variant::VARIANT_MAX; i++) { + argt += "," + Variant::get_type_name(Variant::Type(i)); + } + p_list->push_back(PropertyInfo(Variant::NIL, "Custom data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < custom_data_layers.size(); i++) { + p_list->push_back(PropertyInfo(Variant::STRING, vformat("custom_data_layer_%d/name", i))); + p_list->push_back(PropertyInfo(Variant::INT, vformat("custom_data_layer_%d/type", i), PROPERTY_HINT_ENUM, argt)); + } + + // Sources. + // Note: sources have to be listed in at the end as some TileData rely on the TileSet properties being initialized first. + for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { + p_list->push_back(PropertyInfo(Variant::INT, vformat("sources/%d", E_source->key()), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + } } -void TileSet::tile_clear_shapes(int p_id) { - ERR_FAIL_COND(!tile_map.has(p_id)); - tile_map[p_id].shapes_data.clear(); +void TileSet::_bind_methods() { + // Sources management. + ClassDB::bind_method(D_METHOD("get_next_source_id"), &TileSet::get_next_source_id); + ClassDB::bind_method(D_METHOD("add_source", "atlas_source_id_override"), &TileSet::add_source, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("remove_source", "source_id"), &TileSet::remove_source); + ClassDB::bind_method(D_METHOD("set_source_id", "source_id"), &TileSet::set_source_id); + ClassDB::bind_method(D_METHOD("get_source_count"), &TileSet::get_source_count); + ClassDB::bind_method(D_METHOD("get_source_id", "index"), &TileSet::get_source_id); + ClassDB::bind_method(D_METHOD("has_source", "index"), &TileSet::has_source); + ClassDB::bind_method(D_METHOD("get_source", "index"), &TileSet::get_source); + + // Shape and layout. + ClassDB::bind_method(D_METHOD("set_tile_shape", "shape"), &TileSet::set_tile_shape); + ClassDB::bind_method(D_METHOD("get_tile_shape"), &TileSet::get_tile_shape); + ClassDB::bind_method(D_METHOD("set_tile_layout", "layout"), &TileSet::set_tile_layout); + ClassDB::bind_method(D_METHOD("get_tile_layout"), &TileSet::get_tile_layout); + ClassDB::bind_method(D_METHOD("set_tile_offset_axis", "alignment"), &TileSet::set_tile_offset_axis); + ClassDB::bind_method(D_METHOD("get_tile_offset_axis"), &TileSet::get_tile_offset_axis); + ClassDB::bind_method(D_METHOD("set_tile_size", "size"), &TileSet::set_tile_size); + ClassDB::bind_method(D_METHOD("get_tile_size"), &TileSet::get_tile_size); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "tile_shape", PROPERTY_HINT_ENUM, "Square,Isometric,Half-Offset Square,Hexagon"), "set_tile_shape", "get_tile_shape"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "tile_layout", PROPERTY_HINT_ENUM, "Stacked,Stacked Offset,Stairs Right,Stairs Down,Diamond Right,Diamond Down"), "set_tile_layout", "get_tile_layout"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "tile_offset_axis", PROPERTY_HINT_ENUM, "Horizontal Offset,Vertical Offset"), "set_tile_offset_axis", "get_tile_offset_axis"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "tile_size"), "set_tile_size", "get_tile_size"); + + // Rendering. + ClassDB::bind_method(D_METHOD("set_uv_clipping", "uv_clipping"), &TileSet::set_uv_clipping); + ClassDB::bind_method(D_METHOD("is_uv_clipping"), &TileSet::is_uv_clipping); + ClassDB::bind_method(D_METHOD("set_y_sorting", "y_sorting"), &TileSet::set_y_sorting); + ClassDB::bind_method(D_METHOD("is_y_sorting"), &TileSet::is_y_sorting); + + ClassDB::bind_method(D_METHOD("set_occlusion_layers_count", "occlusion_layers_count"), &TileSet::set_occlusion_layers_count); + ClassDB::bind_method(D_METHOD("get_occlusion_layers_count"), &TileSet::get_occlusion_layers_count); + ClassDB::bind_method(D_METHOD("set_occlusion_layer_light_mask", "layer_index", "light_mask"), &TileSet::set_occlusion_layer_light_mask); + ClassDB::bind_method(D_METHOD("get_occlusion_layer_light_mask"), &TileSet::get_occlusion_layer_light_mask); + ClassDB::bind_method(D_METHOD("set_occlusion_layer_sdf_collision", "layer_index", "sdf_collision"), &TileSet::set_occlusion_layer_sdf_collision); + ClassDB::bind_method(D_METHOD("get_occlusion_layer_sdf_collision"), &TileSet::get_occlusion_layer_sdf_collision); + + // Physics + ClassDB::bind_method(D_METHOD("set_physics_layers_count", "physics_layers_count"), &TileSet::set_physics_layers_count); + ClassDB::bind_method(D_METHOD("get_physics_layers_count"), &TileSet::get_physics_layers_count); + ClassDB::bind_method(D_METHOD("set_physics_layer_collision_layer", "layer_index", "layer"), &TileSet::set_physics_layer_collision_layer); + ClassDB::bind_method(D_METHOD("get_physics_layer_collision_layer", "layer_index"), &TileSet::get_physics_layer_collision_layer); + ClassDB::bind_method(D_METHOD("set_physics_layer_collision_mask", "layer_index", "mask"), &TileSet::set_physics_layer_collision_mask); + ClassDB::bind_method(D_METHOD("get_physics_layer_collision_mask", "layer_index"), &TileSet::get_physics_layer_collision_mask); + ClassDB::bind_method(D_METHOD("set_physics_layer_physics_material", "layer_index", "physics_material"), &TileSet::set_physics_layer_physics_material); + ClassDB::bind_method(D_METHOD("get_physics_layer_physics_material", "layer_index"), &TileSet::get_physics_layer_physics_material); + + // Terrains + ClassDB::bind_method(D_METHOD("set_terrain_sets_count", "terrain_sets_count"), &TileSet::set_terrain_sets_count); + ClassDB::bind_method(D_METHOD("get_terrain_sets_count"), &TileSet::get_terrain_sets_count); + ClassDB::bind_method(D_METHOD("set_terrain_set_mode", "terrain_set", "mode"), &TileSet::set_terrain_set_mode); + ClassDB::bind_method(D_METHOD("get_terrain_set_mode", "terrain_set"), &TileSet::get_terrain_set_mode); + + ClassDB::bind_method(D_METHOD("set_terrains_count", "terrain_set", "terrains_count"), &TileSet::set_terrains_count); + ClassDB::bind_method(D_METHOD("get_terrains_count", "terrain_set"), &TileSet::get_terrains_count); + ClassDB::bind_method(D_METHOD("set_terrain_name", "terrain_set", "terrain_index", "name"), &TileSet::set_terrain_name); + ClassDB::bind_method(D_METHOD("get_terrain_name", "terrain_set", "terrain_index"), &TileSet::get_terrain_name); + ClassDB::bind_method(D_METHOD("set_terrain_color", "terrain_set", "terrain_index", "color"), &TileSet::set_terrain_color); + ClassDB::bind_method(D_METHOD("get_terrain_color", "terrain_set", "terrain_index"), &TileSet::get_terrain_color); + + // Navigation + ClassDB::bind_method(D_METHOD("set_navigation_layers_count", "navigation_layers_count"), &TileSet::set_navigation_layers_count); + ClassDB::bind_method(D_METHOD("get_navigation_layers_count"), &TileSet::get_navigation_layers_count); + ClassDB::bind_method(D_METHOD("set_navigation_layer_layers", "layer_index", "layers"), &TileSet::set_navigation_layer_layers); + ClassDB::bind_method(D_METHOD("get_navigation_layer_layers", "layer_index"), &TileSet::get_navigation_layer_layers); + + // Custom data + ClassDB::bind_method(D_METHOD("set_custom_data_layers_count", "custom_data_layers_count"), &TileSet::set_custom_data_layers_count); + ClassDB::bind_method(D_METHOD("get_custom_data_layers_count"), &TileSet::get_custom_data_layers_count); + + ADD_GROUP("Rendering", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uv_clipping"), "set_uv_clipping", "is_uv_clipping"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "y_sorting"), "set_y_sorting", "is_y_sorting"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "occlusion_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_occlusion_layers_count", "get_occlusion_layers_count"); + + ADD_GROUP("Physics", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_physics_layers_count", "get_physics_layers_count"); + + ADD_GROUP("Terrains", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "terrains_sets_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_terrain_sets_count", "get_terrain_sets_count"); + + ADD_GROUP("Navigation", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_navigation_layers_count", "get_navigation_layers_count"); + + ADD_GROUP("Custom data", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "custom_data_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_custom_data_layers_count", "get_custom_data_layers_count"); + + // -- Enum binding -- + BIND_ENUM_CONSTANT(TILE_SHAPE_SQUARE); + BIND_ENUM_CONSTANT(TILE_SHAPE_ISOMETRIC); + BIND_ENUM_CONSTANT(TILE_SHAPE_HALF_OFFSET_SQUARE); + BIND_ENUM_CONSTANT(TILE_SHAPE_HEXAGON); + + BIND_ENUM_CONSTANT(TILE_LAYOUT_STACKED); + BIND_ENUM_CONSTANT(TILE_LAYOUT_STACKED_OFFSET); + BIND_ENUM_CONSTANT(TILE_LAYOUT_STAIRS_RIGHT); + BIND_ENUM_CONSTANT(TILE_LAYOUT_STAIRS_DOWN); + BIND_ENUM_CONSTANT(TILE_LAYOUT_DIAMOND_RIGHT); + BIND_ENUM_CONSTANT(TILE_LAYOUT_DIAMOND_DOWN); + + BIND_ENUM_CONSTANT(TILE_OFFSET_AXIS_HORIZONTAL); + BIND_ENUM_CONSTANT(TILE_OFFSET_AXIS_VERTICAL); + + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_RIGHT_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_RIGHT_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_LEFT_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_LEFT_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_LEFT_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_LEFT_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_LEFT_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_RIGHT_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_RIGHT_CORNER); + + BIND_ENUM_CONSTANT(TERRAIN_MODE_MATCH_CORNERS_AND_SIDES); + BIND_ENUM_CONSTANT(TERRAIN_MODE_MATCH_CORNERS); + BIND_ENUM_CONSTANT(TERRAIN_MODE_MATCH_SIDES); } -void TileSet::tile_add_shape(int p_id, const Ref<Shape2D> &p_shape, const Transform2D &p_transform, bool p_one_way, const Vector2 &p_autotile_coord) { - ERR_FAIL_COND(!tile_map.has(p_id)); +TileSet::TileSet() { + // Instantiate the tile meshes. + tile_lines_mesh.instantiate(); + tile_filled_mesh.instantiate(); + + // Instanciate and list all plugins. + tile_set_plugins_vector.append(memnew(TileSetPluginAtlasRendering)); + tile_set_plugins_vector.append(memnew(TileSetPluginAtlasPhysics)); + tile_set_plugins_vector.append(memnew(TileSetPluginAtlasNavigation)); + tile_set_plugins_vector.append(memnew(TileSetPluginScenesCollections)); +} - ShapeData new_data = ShapeData(); - new_data.shape = p_shape; - new_data.shape_transform = p_transform; - new_data.one_way_collision = p_one_way; - new_data.autotile_coord = p_autotile_coord; +TileSet::~TileSet() { +#ifndef DISABLE_DEPRECATED + for (Map<int, CompatibilityTileData *>::Element *E = compatibility_data.front(); E; E = E->next()) { + memdelete(E->get()); + } +#endif // DISABLE_DEPRECATED + while (!source_ids.is_empty()) { + remove_source(source_ids[0]); + } + for (int i = 0; i < tile_set_plugins_vector.size(); i++) { + memdelete(tile_set_plugins_vector[i]); + } +} - tile_map[p_id].shapes_data.push_back(new_data); +/////////////////////////////// TileSetSource ////////////////////////////////////// + +void TileSetSource::set_tile_set(const TileSet *p_tile_set) { + tile_set = p_tile_set; } -int TileSet::tile_get_shape_count(int p_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), 0); - return tile_map[p_id].shapes_data.size(); +/////////////////////////////// TileSetAtlasSource ////////////////////////////////////// + +void TileSetAtlasSource::set_tile_set(const TileSet *p_tile_set) { + tile_set = p_tile_set; + + // Set the TileSet on all TileData. + for (Map<Vector2i, TileAlternativesData>::Element *E_tile = tiles.front(); E_tile; E_tile = E_tile->next()) { + for (Map<int, TileData *>::Element *E_alternative = E_tile->get().alternatives.front(); E_alternative; E_alternative = E_alternative->next()) { + E_alternative->get()->set_tile_set(tile_set); + } + } } -void TileSet::tile_set_shape(int p_id, int p_shape_id, const Ref<Shape2D> &p_shape) { - ERR_FAIL_COND(!tile_map.has(p_id)); - ERR_FAIL_COND(p_shape_id < 0); +void TileSetAtlasSource::notify_tile_data_properties_should_change() { + // Set the TileSet on all TileData. + for (Map<Vector2i, TileAlternativesData>::Element *E_tile = tiles.front(); E_tile; E_tile = E_tile->next()) { + for (Map<int, TileData *>::Element *E_alternative = E_tile->get().alternatives.front(); E_alternative; E_alternative = E_alternative->next()) { + E_alternative->get()->notify_tile_data_properties_should_change(); + } + } +} - if (p_shape_id >= tile_map[p_id].shapes_data.size()) { - tile_map[p_id].shapes_data.resize(p_shape_id + 1); +void TileSetAtlasSource::reset_state() { + // Reset all TileData. + for (Map<Vector2i, TileAlternativesData>::Element *E_tile = tiles.front(); E_tile; E_tile = E_tile->next()) { + for (Map<int, TileData *>::Element *E_alternative = E_tile->get().alternatives.front(); E_alternative; E_alternative = E_alternative->next()) { + E_alternative->get()->reset_state(); + } } - tile_map[p_id].shapes_data.write[p_shape_id].shape = p_shape; - _decompose_convex_shape(p_shape); +} + +void TileSetAtlasSource::set_texture(Ref<Texture2D> p_texture) { + texture = p_texture; + emit_changed(); } -Ref<Shape2D> TileSet::tile_get_shape(int p_id, int p_shape_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), Ref<Shape2D>()); - ERR_FAIL_COND_V(p_shape_id < 0, Ref<Shape2D>()); +Ref<Texture2D> TileSetAtlasSource::get_texture() const { + return texture; +} - if (p_shape_id < tile_map[p_id].shapes_data.size()) { - return tile_map[p_id].shapes_data[p_shape_id].shape; +void TileSetAtlasSource::set_margins(Vector2i p_margins) { + if (p_margins.x < 0 || p_margins.y < 0) { + WARN_PRINT("Atlas source margins should be positive."); + margins = Vector2i(MAX(0, p_margins.x), MAX(0, p_margins.y)); + } else { + margins = p_margins; } - return Ref<Shape2D>(); + emit_changed(); } +Vector2i TileSetAtlasSource::get_margins() const { + return margins; +} + +void TileSetAtlasSource::set_separation(Vector2i p_separation) { + if (p_separation.x < 0 || p_separation.y < 0) { + WARN_PRINT("Atlas source separation should be positive."); + separation = Vector2i(MAX(0, p_separation.x), MAX(0, p_separation.y)); + } else { + separation = p_separation; + } -void TileSet::tile_set_shape_transform(int p_id, int p_shape_id, const Transform2D &p_offset) { - ERR_FAIL_COND(!tile_map.has(p_id)); - ERR_FAIL_COND(p_shape_id < 0); + emit_changed(); +} +Vector2i TileSetAtlasSource::get_separation() const { + return separation; +} - if (p_shape_id >= tile_map[p_id].shapes_data.size()) { - tile_map[p_id].shapes_data.resize(p_shape_id + 1); +void TileSetAtlasSource::set_texture_region_size(Vector2i p_tile_size) { + if (p_tile_size.x <= 0 || p_tile_size.y <= 0) { + WARN_PRINT("Atlas source tile_size should be strictly positive."); + texture_region_size = Vector2i(MAX(1, p_tile_size.x), MAX(1, p_tile_size.y)); + } else { + texture_region_size = p_tile_size; } - tile_map[p_id].shapes_data.write[p_shape_id].shape_transform = p_offset; + emit_changed(); } +Vector2i TileSetAtlasSource::get_texture_region_size() const { + return texture_region_size; +} + +Vector2i TileSetAtlasSource::get_atlas_grid_size() const { + Ref<Texture2D> texture = get_texture(); + if (!texture.is_valid()) { + return Vector2i(); + } + + ERR_FAIL_COND_V(texture_region_size.x <= 0 || texture_region_size.y <= 0, Vector2i()); + + Size2i valid_area = texture->get_size() - margins; + + // Compute the number of valid tiles in the tiles atlas + Size2i grid_size = Size2i(); + if (valid_area.x >= texture_region_size.x && valid_area.y >= texture_region_size.y) { + valid_area -= texture_region_size; + grid_size = Size2i(1, 1) + valid_area / (texture_region_size + separation); + } + return grid_size; +} + +bool TileSetAtlasSource::_set(const StringName &p_name, const Variant &p_value) { + Vector<String> components = String(p_name).split("/", true, 2); + + // Compute the vector2i if we have coordinates. + Vector<String> coords_split = components[0].split(":"); + Vector2i coords = TileSetSource::INVALID_ATLAS_COORDS; + if (coords_split.size() == 2 && coords_split[0].is_valid_int() && coords_split[1].is_valid_int()) { + coords = Vector2i(coords_split[0].to_int(), coords_split[1].to_int()); + } + + // Properties. + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + // Create the tile if needed. + if (!has_tile(coords)) { + create_tile(coords); + } + if (components.size() >= 2) { + // Properties. + if (components[1] == "size_in_atlas") { + move_tile_in_atlas(coords, coords, p_value); + } else if (components[1] == "next_alternative_id") { + tiles[coords].next_alternative_id = p_value; + } else if (components[1].is_valid_int()) { + int alternative_id = components[1].to_int(); + if (alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE) { + // Create the alternative if needed ? + if (!has_alternative_tile(coords, alternative_id)) { + create_alternative_tile(coords, alternative_id); + } + if (!tiles[coords].alternatives.has(alternative_id)) { + tiles[coords].alternatives[alternative_id] = memnew(TileData); + tiles[coords].alternatives[alternative_id]->set_tile_set(tile_set); + tiles[coords].alternatives[alternative_id]->set_allow_transform(alternative_id > 0); + tiles[coords].alternatives_ids.append(alternative_id); + } + if (components.size() >= 3) { + bool valid; + tiles[coords].alternatives[alternative_id]->set(components[2], p_value, &valid); + return valid; + } else { + // Only create the alternative if it did not exist yet. + return true; + } + } + } + } + } + + return false; +} + +bool TileSetAtlasSource::_get(const StringName &p_name, Variant &r_ret) const { + Vector<String> components = String(p_name).split("/", true, 2); + + // Properties. + Vector<String> coords_split = components[0].split(":"); + if (coords_split.size() == 2 && coords_split[0].is_valid_int() && coords_split[1].is_valid_int()) { + Vector2i coords = Vector2i(coords_split[0].to_int(), coords_split[1].to_int()); + if (tiles.has(coords)) { + if (components.size() >= 2) { + // Properties. + if (components[1] == "size_in_atlas") { + r_ret = tiles[coords].size_in_atlas; + return true; + } else if (components[1] == "next_alternative_id") { + r_ret = tiles[coords].next_alternative_id; + return true; + } else if (components[1].is_valid_int()) { + int alternative_id = components[1].to_int(); + if (alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE && tiles[coords].alternatives.has(alternative_id)) { + if (components.size() >= 3) { + bool valid; + r_ret = tiles[coords].alternatives[alternative_id]->get(components[2], &valid); + return valid; + } else { + // Only to notify the tile alternative exists. + r_ret = alternative_id; + return true; + } + } + } + } + } + } + + return false; +} + +void TileSetAtlasSource::_get_property_list(List<PropertyInfo> *p_list) const { + // Atlases data. + PropertyInfo property_info; + for (Map<Vector2i, TileAlternativesData>::Element *E_tile = tiles.front(); E_tile; E_tile = E_tile->next()) { + List<PropertyInfo> tile_property_list; + + // size_in_atlas + property_info = PropertyInfo(Variant::VECTOR2I, "size_in_atlas", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR); + if (E_tile->get().size_in_atlas == Vector2i(1, 1)) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + tile_property_list.push_back(property_info); + + // next_alternative_id + property_info = PropertyInfo(Variant::INT, "next_alternative_id", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR); + if (E_tile->get().next_alternative_id == 1) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + tile_property_list.push_back(property_info); + + for (Map<int, TileData *>::Element *E_alternative = E_tile->get().alternatives.front(); E_alternative; E_alternative = E_alternative->next()) { + // Add a dummy property to show the alternative exists. + tile_property_list.push_back(PropertyInfo(Variant::INT, vformat("%d", E_alternative->key()), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + + // Get the alternative tile's properties and append them to the list of properties. + List<PropertyInfo> alternative_property_list; + E_alternative->get()->get_property_list(&alternative_property_list); + for (List<PropertyInfo>::Element *E_property = alternative_property_list.front(); E_property; E_property = E_property->next()) { + property_info = E_property->get(); + bool valid; + Variant default_value = ClassDB::class_get_default_property_value("TileData", property_info.name, &valid); + Variant value = E_alternative->get()->get(property_info.name); + if (valid && value == default_value) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + property_info.name = vformat("%s/%s", vformat("%d", E_alternative->key()), property_info.name); + tile_property_list.push_back(property_info); + } + } + + // Add all alternative. + for (List<PropertyInfo>::Element *E_property = tile_property_list.front(); E_property; E_property = E_property->next()) { + E_property->get().name = vformat("%s/%s", vformat("%d:%d", E_tile->key().x, E_tile->key().y), E_property->get().name); + p_list->push_back(E_property->get()); + } + } +} -Transform2D TileSet::tile_get_shape_transform(int p_id, int p_shape_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), Transform2D()); - ERR_FAIL_COND_V(p_shape_id < 0, Transform2D()); +void TileSetAtlasSource::create_tile(const Vector2i p_atlas_coords, const Vector2i p_size) { + // Create a tile if it does not exists. + ERR_FAIL_COND(p_atlas_coords.x < 0 || p_atlas_coords.y < 0); + ERR_FAIL_COND(p_size.x <= 0 || p_size.y <= 0); + for (int x = 0; x < p_size.x; x++) { + for (int y = 0; y < p_size.y; y++) { + Vector2i coords = p_atlas_coords + Vector2i(x, y); + ERR_FAIL_COND_MSG(tiles.has(coords), vformat("Cannot create tile at position %s with size %s. Already a tile present at %s.", p_atlas_coords, p_size, coords)); + } + } - if (p_shape_id < tile_map[p_id].shapes_data.size()) { - return tile_map[p_id].shapes_data[p_shape_id].shape_transform; + // Create and resize the tile. + tiles.insert(p_atlas_coords, TileSetAtlasSource::TileAlternativesData()); + tiles_ids.append(p_atlas_coords); + tiles_ids.sort(); + + tiles[p_atlas_coords].size_in_atlas = p_size; + tiles[p_atlas_coords].alternatives[0] = memnew(TileData); + tiles[p_atlas_coords].alternatives[0]->set_tile_set(tile_set); + tiles[p_atlas_coords].alternatives[0]->set_allow_transform(false); + tiles[p_atlas_coords].alternatives[0]->connect("changed", callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed)); + tiles[p_atlas_coords].alternatives[0]->notify_property_list_changed(); + tiles[p_atlas_coords].alternatives_ids.append(0); + + // Add all covered positions to the mapping cache + for (int x = 0; x < p_size.x; x++) { + for (int y = 0; y < p_size.y; y++) { + Vector2i coords = p_atlas_coords + Vector2i(x, y); + _coords_mapping_cache[coords] = p_atlas_coords; + } } - return Transform2D(); + emit_signal("changed"); } -void TileSet::tile_set_shape_offset(int p_id, int p_shape_id, const Vector2 &p_offset) { - Transform2D transform = tile_get_shape_transform(p_id, p_shape_id); - transform.set_origin(p_offset); - tile_set_shape_transform(p_id, p_shape_id, transform); +void TileSetAtlasSource::remove_tile(Vector2i p_atlas_coords) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + + // Remove all covered positions from the mapping cache + Size2i size = tiles[p_atlas_coords].size_in_atlas; + + for (int x = 0; x < size.x; x++) { + for (int y = 0; y < size.y; y++) { + Vector2i coords = p_atlas_coords + Vector2i(x, y); + _coords_mapping_cache.erase(coords); + } + } + + // Free tile data. + for (Map<int, TileData *>::Element *E_tile_data = tiles[p_atlas_coords].alternatives.front(); E_tile_data; E_tile_data = E_tile_data->next()) { + memdelete(E_tile_data->get()); + } + + // Delete the tile + tiles.erase(p_atlas_coords); + tiles_ids.erase(p_atlas_coords); + tiles_ids.sort(); + + emit_signal("changed"); } -Vector2 TileSet::tile_get_shape_offset(int p_id, int p_shape_id) const { - return tile_get_shape_transform(p_id, p_shape_id).get_origin(); +bool TileSetAtlasSource::has_tile(Vector2i p_atlas_coords) const { + return tiles.has(p_atlas_coords); +} + +Vector2i TileSetAtlasSource::get_tile_at_coords(Vector2i p_atlas_coords) const { + if (!_coords_mapping_cache.has(p_atlas_coords)) { + return INVALID_ATLAS_COORDS; + } + + return _coords_mapping_cache[p_atlas_coords]; } -void TileSet::tile_set_shape_one_way(int p_id, int p_shape_id, const bool p_one_way) { - ERR_FAIL_COND(!tile_map.has(p_id)); - ERR_FAIL_COND(p_shape_id < 0); +Vector2i TileSetAtlasSource::get_tile_size_in_atlas(Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Vector2i(-1, -1), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + + return tiles[p_atlas_coords].size_in_atlas; +} + +int TileSetAtlasSource::get_tiles_count() const { + return tiles_ids.size(); +} + +Vector2i TileSetAtlasSource::get_tile_id(int p_index) const { + ERR_FAIL_INDEX_V(p_index, tiles_ids.size(), TileSetSource::INVALID_ATLAS_COORDS); + return tiles_ids[p_index]; +} + +Rect2i TileSetAtlasSource::get_tile_texture_region(Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Rect2i(), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + + Vector2i size_in_atlas = tiles[p_atlas_coords].size_in_atlas; + Vector2 region_size = texture_region_size * size_in_atlas + separation * (size_in_atlas - Vector2i(1, 1)); - if (p_shape_id >= tile_map[p_id].shapes_data.size()) { - tile_map[p_id].shapes_data.resize(p_shape_id + 1); + Vector2 origin = margins + (p_atlas_coords * (texture_region_size + separation)); + + return Rect2(origin, region_size); + ; +} + +Vector2i TileSetAtlasSource::get_tile_effective_texture_offset(Vector2i p_atlas_coords, int p_alternative_tile) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Vector2i(), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_COND_V_MSG(!has_alternative_tile(p_atlas_coords, p_alternative_tile), Vector2i(), vformat("TileSetAtlasSource has no alternative tile with id %d at %s.", p_alternative_tile, String(p_atlas_coords))); + ERR_FAIL_COND_V(!tile_set, Vector2i()); + + Vector2 margin = (get_tile_texture_region(p_atlas_coords).size - tile_set->get_tile_size()) / 2; + margin = Vector2i(MAX(0, margin.x), MAX(0, margin.y)); + Vector2i effective_texture_offset = Object::cast_to<TileData>(get_tile_data(p_atlas_coords, p_alternative_tile))->get_texture_offset(); + if (ABS(effective_texture_offset.x) > margin.x || ABS(effective_texture_offset.y) > margin.y) { + effective_texture_offset.x = CLAMP(effective_texture_offset.x, -margin.x, margin.x); + effective_texture_offset.y = CLAMP(effective_texture_offset.y, -margin.y, margin.y); } - tile_map[p_id].shapes_data.write[p_shape_id].one_way_collision = p_one_way; - emit_changed(); + + return effective_texture_offset; } -bool TileSet::tile_get_shape_one_way(int p_id, int p_shape_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), false); - ERR_FAIL_COND_V(p_shape_id < 0, false); +bool TileSetAtlasSource::can_move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords, Vector2i p_new_size) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), false, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); - if (p_shape_id < tile_map[p_id].shapes_data.size()) { - return tile_map[p_id].shapes_data[p_shape_id].one_way_collision; + Vector2i new_atlas_coords = (p_new_atlas_coords != INVALID_ATLAS_COORDS) ? p_new_atlas_coords : p_atlas_coords; + if (new_atlas_coords.x < 0 || new_atlas_coords.y < 0) { + return false; } - return false; + Vector2i size = (p_new_size != Vector2i(-1, -1)) ? p_new_size : tiles[p_atlas_coords].size_in_atlas; + ERR_FAIL_COND_V(size.x <= 0 || size.y <= 0, false); + + Size2i grid_size = get_atlas_grid_size(); + if (new_atlas_coords.x + size.x > grid_size.x || new_atlas_coords.y + size.y > grid_size.y) { + return false; + } + + Rect2i new_rect = Rect2i(new_atlas_coords, size); + // Check if the new tile can fit in the new rect. + for (int x = new_rect.position.x; x < new_rect.get_end().x; x++) { + for (int y = new_rect.position.y; y < new_rect.get_end().y; y++) { + Vector2i coords = get_tile_at_coords(Vector2i(x, y)); + if (coords != p_atlas_coords && coords != TileSetSource::INVALID_ATLAS_COORDS) { + return false; + } + } + } + + return true; +} + +void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords, Vector2i p_new_size) { + bool can_move = can_move_tile_in_atlas(p_atlas_coords, p_new_atlas_coords, p_new_size); + ERR_FAIL_COND_MSG(!can_move, vformat("Cannot move tile at position %s with size %s. Tile already present.", p_new_atlas_coords, p_new_size)); + + // Compute the actual new rect from arguments. + Vector2i new_atlas_coords = (p_new_atlas_coords != INVALID_ATLAS_COORDS) ? p_new_atlas_coords : p_atlas_coords; + Vector2i size = (p_new_size != Vector2i(-1, -1)) ? p_new_size : tiles[p_atlas_coords].size_in_atlas; + + if (new_atlas_coords == p_atlas_coords && size == tiles[p_atlas_coords].size_in_atlas) { + return; + } + + // Remove all covered positions from the mapping cache. + Size2i old_size = tiles[p_atlas_coords].size_in_atlas; + for (int x = 0; x < old_size.x; x++) { + for (int y = 0; y < old_size.y; y++) { + Vector2i coords = p_atlas_coords + Vector2i(x, y); + _coords_mapping_cache.erase(coords); + } + } + + // Move the tile and update its size. + if (new_atlas_coords != p_atlas_coords) { + tiles[new_atlas_coords] = tiles[p_atlas_coords]; + tiles.erase(p_atlas_coords); + + tiles_ids.erase(p_atlas_coords); + tiles_ids.append(new_atlas_coords); + tiles_ids.sort(); + } + tiles[new_atlas_coords].size_in_atlas = size; + + // Add all covered positions to the mapping cache again. + for (int x = 0; x < size.x; x++) { + for (int y = 0; y < size.y; y++) { + Vector2i coords = new_atlas_coords + Vector2i(x, y); + _coords_mapping_cache[coords] = new_atlas_coords; + } + } + + emit_signal("changed"); } -void TileSet::tile_set_shape_one_way_margin(int p_id, int p_shape_id, float p_margin) { - ERR_FAIL_COND(!tile_map.has(p_id)); - ERR_FAIL_COND(p_shape_id < 0); +bool TileSetAtlasSource::has_tiles_outside_texture() { + Vector2i grid_size = get_atlas_grid_size(); + Vector<Vector2i> to_remove; - if (p_shape_id >= tile_map[p_id].shapes_data.size()) { - tile_map[p_id].shapes_data.resize(p_shape_id + 1); + for (Map<Vector2i, TileSetAtlasSource::TileAlternativesData>::Element *E = tiles.front(); E; E = E->next()) { + if (E->key().x >= grid_size.x || E->key().y >= grid_size.y) { + return true; + } } - tile_map[p_id].shapes_data.write[p_shape_id].one_way_collision_margin = p_margin; - emit_changed(); + + return false; } -float TileSet::tile_get_shape_one_way_margin(int p_id, int p_shape_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), 0); - ERR_FAIL_COND_V(p_shape_id < 0, 0); +void TileSetAtlasSource::clear_tiles_outside_texture() { + Vector2i grid_size = get_atlas_grid_size(); + Vector<Vector2i> to_remove; + + for (Map<Vector2i, TileSetAtlasSource::TileAlternativesData>::Element *E = tiles.front(); E; E = E->next()) { + if (E->key().x >= grid_size.x || E->key().y >= grid_size.y) { + to_remove.append(E->key()); + } + } - if (p_shape_id < tile_map[p_id].shapes_data.size()) { - return tile_map[p_id].shapes_data[p_shape_id].one_way_collision_margin; + for (int i = 0; i < to_remove.size(); i++) { + remove_tile(to_remove[i]); } +} + +int TileSetAtlasSource::create_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_id_override) { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), -1, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_COND_V_MSG(p_alternative_id_override >= 0 && tiles[p_atlas_coords].alternatives.has(p_alternative_id_override), -1, vformat("Cannot create alternative tile. Another alternative exists with id %d.", p_alternative_id_override)); + + int new_alternative_id = p_alternative_id_override >= 0 ? p_alternative_id_override : tiles[p_atlas_coords].next_alternative_id; + + tiles[p_atlas_coords].alternatives[new_alternative_id] = memnew(TileData); + tiles[p_atlas_coords].alternatives[new_alternative_id]->set_tile_set(tile_set); + tiles[p_atlas_coords].alternatives[new_alternative_id]->set_allow_transform(true); + tiles[p_atlas_coords].alternatives[new_alternative_id]->notify_property_list_changed(); + tiles[p_atlas_coords].alternatives_ids.append(new_alternative_id); + tiles[p_atlas_coords].alternatives_ids.sort(); + _compute_next_alternative_id(p_atlas_coords); - return 0; + emit_signal("changed"); + + return new_alternative_id; +} + +void TileSetAtlasSource::remove_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_COND_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); + ERR_FAIL_COND_MSG(p_alternative_tile == 0, "Cannot remove the alternative with id 0, the base tile alternative cannot be removed."); + + memdelete(tiles[p_atlas_coords].alternatives[p_alternative_tile]); + tiles[p_atlas_coords].alternatives.erase(p_alternative_tile); + tiles[p_atlas_coords].alternatives_ids.erase(p_alternative_tile); + tiles[p_atlas_coords].alternatives_ids.sort(); + + emit_signal("changed"); } -void TileSet::tile_set_light_occluder(int p_id, const Ref<OccluderPolygon2D> &p_light_occluder) { - ERR_FAIL_COND(!tile_map.has(p_id)); - tile_map[p_id].occluder = p_light_occluder; +void TileSetAtlasSource::set_alternative_tile_id(const Vector2i p_atlas_coords, int p_alternative_tile, int p_new_id) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_COND_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); + ERR_FAIL_COND_MSG(p_alternative_tile == 0, "Cannot change the alternative with id 0, the base tile alternative cannot be modified."); + + ERR_FAIL_COND_MSG(tiles[p_atlas_coords].alternatives.has(p_new_id), vformat("TileSetAtlasSource has already an alternative with id %d at %s.", p_new_id, String(p_atlas_coords))); + + tiles[p_atlas_coords].alternatives[p_new_id] = tiles[p_atlas_coords].alternatives[p_alternative_tile]; + tiles[p_atlas_coords].alternatives_ids.append(p_new_id); + + tiles[p_atlas_coords].alternatives.erase(p_alternative_tile); + tiles[p_atlas_coords].alternatives_ids.erase(p_alternative_tile); + tiles[p_atlas_coords].alternatives_ids.sort(); + + emit_signal("changed"); } -Ref<OccluderPolygon2D> TileSet::tile_get_light_occluder(int p_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), Ref<OccluderPolygon2D>()); - return tile_map[p_id].occluder; +bool TileSetAtlasSource::has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), false, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); + return tiles[p_atlas_coords].alternatives.has(p_alternative_tile); } -void TileSet::autotile_set_light_occluder(int p_id, const Ref<OccluderPolygon2D> &p_light_occluder, const Vector2 &p_coord) { - ERR_FAIL_COND(!tile_map.has(p_id)); - if (p_light_occluder.is_null()) { - if (tile_map[p_id].autotile_data.occluder_map.has(p_coord)) { - tile_map[p_id].autotile_data.occluder_map.erase(p_coord); +int TileSetAtlasSource::get_next_alternative_tile_id(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), -1, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); + return tiles[p_atlas_coords].next_alternative_id; +} + +int TileSetAtlasSource::get_alternative_tiles_count(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), -1, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); + return tiles[p_atlas_coords].alternatives_ids.size(); +} + +int TileSetAtlasSource::get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), -1, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_INDEX_V(p_index, tiles[p_atlas_coords].alternatives_ids.size(), -1); + + return tiles[p_atlas_coords].alternatives_ids[p_index]; +} + +Object *TileSetAtlasSource::get_tile_data(const Vector2i p_atlas_coords, int p_alternative_tile) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), nullptr, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_COND_V_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), nullptr, vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); + + return tiles[p_atlas_coords].alternatives[p_alternative_tile]; +} + +void TileSetAtlasSource::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_texture", "texture"), &TileSetAtlasSource::set_texture); + ClassDB::bind_method(D_METHOD("get_texture"), &TileSetAtlasSource::get_texture); + ClassDB::bind_method(D_METHOD("set_margins", "margins"), &TileSetAtlasSource::set_margins); + ClassDB::bind_method(D_METHOD("get_margins"), &TileSetAtlasSource::get_margins); + ClassDB::bind_method(D_METHOD("set_separation", "separation"), &TileSetAtlasSource::set_separation); + ClassDB::bind_method(D_METHOD("get_separation"), &TileSetAtlasSource::get_separation); + ClassDB::bind_method(D_METHOD("set_texture_region_size", "texture_region_size"), &TileSetAtlasSource::set_texture_region_size); + ClassDB::bind_method(D_METHOD("get_texture_region_size"), &TileSetAtlasSource::get_texture_region_size); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_NOEDITOR), "set_texture", "get_texture"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "margins", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_margins", "get_margins"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_separation", "get_separation"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "tile_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_texture_region_size", "get_texture_region_size"); + + // Base tiles + ClassDB::bind_method(D_METHOD("create_tile", "atlas_coords", "size"), &TileSetAtlasSource::create_tile, DEFVAL(Vector2i(1, 1))); + ClassDB::bind_method(D_METHOD("remove_tile", "atlas_coords"), &TileSetAtlasSource::remove_tile); // Remove a tile. If p_tile_key.alternative_tile if different from 0, remove the alternative + ClassDB::bind_method(D_METHOD("has_tile", "atlas_coords"), &TileSetAtlasSource::has_tile); + ClassDB::bind_method(D_METHOD("can_move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::can_move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1))); + ClassDB::bind_method(D_METHOD("move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1))); + ClassDB::bind_method(D_METHOD("get_tile_size_in_atlas", "atlas_coords"), &TileSetAtlasSource::get_tile_size_in_atlas); + + ClassDB::bind_method(D_METHOD("get_tiles_count"), &TileSetAtlasSource::get_tiles_count); + ClassDB::bind_method(D_METHOD("get_tile_id", "index"), &TileSetAtlasSource::get_tile_id); + + ClassDB::bind_method(D_METHOD("get_tile_at_coords", "atlas_coords"), &TileSetAtlasSource::get_tile_at_coords); + + // Alternative tiles + ClassDB::bind_method(D_METHOD("create_alternative_tile", "atlas_coords", "alternative_id_override"), &TileSetAtlasSource::create_alternative_tile, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("remove_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::remove_alternative_tile); + ClassDB::bind_method(D_METHOD("set_alternative_tile_id", "atlas_coords", "alternative_tile", "new_id"), &TileSetAtlasSource::set_alternative_tile_id); + ClassDB::bind_method(D_METHOD("has_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::has_alternative_tile); + ClassDB::bind_method(D_METHOD("get_next_alternative_tile_id", "atlas_coords"), &TileSetAtlasSource::get_next_alternative_tile_id); + + ClassDB::bind_method(D_METHOD("get_alternative_tiles_count", "atlas_coords"), &TileSetAtlasSource::get_alternative_tiles_count); + ClassDB::bind_method(D_METHOD("get_alternative_tile_id", "atlas_coords", "index"), &TileSetAtlasSource::get_alternative_tile_id); + + ClassDB::bind_method(D_METHOD("get_tile_data", "atlas_coords", "index"), &TileSetAtlasSource::get_tile_data); + + // Helpers. + ClassDB::bind_method(D_METHOD("get_atlas_grid_size"), &TileSetAtlasSource::get_atlas_grid_size); + ClassDB::bind_method(D_METHOD("has_tiles_outside_texture"), &TileSetAtlasSource::has_tiles_outside_texture); + ClassDB::bind_method(D_METHOD("clear_tiles_outside_texture"), &TileSetAtlasSource::clear_tiles_outside_texture); + ClassDB::bind_method(D_METHOD("get_tile_texture_region", "atlas_coords"), &TileSetAtlasSource::get_tile_texture_region); +} + +TileSetAtlasSource::~TileSetAtlasSource() { + // Free everything needed. + for (Map<Vector2i, TileAlternativesData>::Element *E_alternatives = tiles.front(); E_alternatives; E_alternatives = E_alternatives->next()) { + for (Map<int, TileData *>::Element *E_tile_data = E_alternatives->get().alternatives.front(); E_tile_data; E_tile_data = E_tile_data->next()) { + memdelete(E_tile_data->get()); } - } else { - tile_map[p_id].autotile_data.occluder_map[p_coord] = p_light_occluder; } } -Ref<OccluderPolygon2D> TileSet::autotile_get_light_occluder(int p_id, const Vector2 &p_coord) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), Ref<OccluderPolygon2D>()); +TileData *TileSetAtlasSource::_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile) { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), nullptr, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_COND_V_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), nullptr, vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); + + return tiles[p_atlas_coords].alternatives[p_alternative_tile]; +} + +const TileData *TileSetAtlasSource::_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), nullptr, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_COND_V_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), nullptr, vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); + + return tiles[p_atlas_coords].alternatives[p_alternative_tile]; +} + +void TileSetAtlasSource::_compute_next_alternative_id(const Vector2i p_atlas_coords) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + + while (tiles[p_atlas_coords].alternatives.has(tiles[p_atlas_coords].next_alternative_id)) { + tiles[p_atlas_coords].next_alternative_id = (tiles[p_atlas_coords].next_alternative_id % 1073741823) + 1; // 2 ** 30 + }; +} + +/////////////////////////////// TileSetScenesCollectionSource ////////////////////////////////////// + +void TileSetScenesCollectionSource::_compute_next_alternative_id() { + while (scenes.has(next_scene_id)) { + next_scene_id = (next_scene_id % 1073741823) + 1; // 2 ** 30 + }; +} + +int TileSetScenesCollectionSource::get_tiles_count() const { + return 1; +} + +Vector2i TileSetScenesCollectionSource::get_tile_id(int p_tile_index) const { + ERR_FAIL_COND_V(p_tile_index != 0, TileSetSource::INVALID_ATLAS_COORDS); + return Vector2i(); +} + +bool TileSetScenesCollectionSource::has_tile(Vector2i p_atlas_coords) const { + return p_atlas_coords == Vector2i(); +} + +int TileSetScenesCollectionSource::get_alternative_tiles_count(const Vector2i p_atlas_coords) const { + return scenes_ids.size(); +} + +int TileSetScenesCollectionSource::get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const { + ERR_FAIL_COND_V(p_atlas_coords != Vector2i(), TileSetSource::INVALID_TILE_ALTERNATIVE); + ERR_FAIL_INDEX_V(p_index, scenes_ids.size(), TileSetSource::INVALID_TILE_ALTERNATIVE); + + return scenes_ids[p_index]; +} + +bool TileSetScenesCollectionSource::has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const { + ERR_FAIL_COND_V(p_atlas_coords != Vector2i(), false); + return scenes.has(p_alternative_tile); +} + +int TileSetScenesCollectionSource::create_scene_tile(Ref<PackedScene> p_packed_scene, int p_id_override) { + ERR_FAIL_COND_V_MSG(p_id_override >= 0 && scenes.has(p_id_override), -1, vformat("Cannot create scene tile. Another scene tile exists with id %d.", p_id_override)); + + int new_scene_id = p_id_override >= 0 ? p_id_override : next_scene_id; + + scenes[new_scene_id] = SceneData(); + scenes_ids.append(new_scene_id); + scenes_ids.sort(); + set_scene_tile_scene(new_scene_id, p_packed_scene); + _compute_next_alternative_id(); + + emit_signal("changed"); + + return new_scene_id; +} + +void TileSetScenesCollectionSource::set_scene_tile_id(int p_id, int p_new_id) { + ERR_FAIL_COND(p_new_id < 0); + ERR_FAIL_COND(!has_scene_tile_id(p_id)); + ERR_FAIL_COND(has_scene_tile_id(p_new_id)); + + scenes[p_new_id] = SceneData(); + scenes[p_new_id] = scenes[p_id]; + scenes_ids.append(p_new_id); + scenes_ids.sort(); + + _compute_next_alternative_id(); + + scenes.erase(p_id); + scenes_ids.erase(p_id); + + emit_signal("changed"); +} + +void TileSetScenesCollectionSource::set_scene_tile_scene(int p_id, Ref<PackedScene> p_packed_scene) { + ERR_FAIL_COND(!scenes.has(p_id)); + if (p_packed_scene.is_valid()) { + // Make sure we have a root node. Supposed to be at 0 index because find_node_by_path() does not seem to work. + ERR_FAIL_COND(!p_packed_scene->get_state().is_valid()); + ERR_FAIL_COND(p_packed_scene->get_state()->get_node_count() < 1); - if (!tile_map[p_id].autotile_data.occluder_map.has(p_coord)) { - return Ref<OccluderPolygon2D>(); + // Check if it extends CanvasItem. + String type = p_packed_scene->get_state()->get_node_type(0); + bool extends_correct_class = ClassDB::is_parent_class(type, "Control") || ClassDB::is_parent_class(type, "Node2D"); + ERR_FAIL_COND_MSG(!extends_correct_class, vformat("Invalid PackedScene for TileSetScenesCollectionSource: %s. Root node should extend Control or Node2D.", p_packed_scene->get_path())); + + scenes[p_id].scene = p_packed_scene; } else { - return tile_map[p_id].autotile_data.occluder_map[p_coord]; + scenes[p_id].scene = Ref<PackedScene>(); } + emit_signal("changed"); } -void TileSet::tile_set_navigation_polygon_offset(int p_id, const Vector2 &p_offset) { - ERR_FAIL_COND(!tile_map.has(p_id)); - tile_map[p_id].navigation_polygon_offset = p_offset; +Ref<PackedScene> TileSetScenesCollectionSource::get_scene_tile_scene(int p_id) const { + ERR_FAIL_COND_V(!scenes.has(p_id), Ref<PackedScene>()); + return scenes[p_id].scene; } -Vector2 TileSet::tile_get_navigation_polygon_offset(int p_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), Vector2()); - return tile_map[p_id].navigation_polygon_offset; +void TileSetScenesCollectionSource::set_scene_tile_display_placeholder(int p_id, bool p_display_placeholder) { + ERR_FAIL_COND(!scenes.has(p_id)); + + scenes[p_id].display_placeholder = p_display_placeholder; + + emit_signal("changed"); } -void TileSet::tile_set_navigation_polygon(int p_id, const Ref<NavigationPolygon> &p_navigation_polygon) { - ERR_FAIL_COND(!tile_map.has(p_id)); - tile_map[p_id].navigation_polygon = p_navigation_polygon; +bool TileSetScenesCollectionSource::get_scene_tile_display_placeholder(int p_id) const { + ERR_FAIL_COND_V(!scenes.has(p_id), false); + return scenes[p_id].display_placeholder; } -Ref<NavigationPolygon> TileSet::tile_get_navigation_polygon(int p_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), Ref<NavigationPolygon>()); - return tile_map[p_id].navigation_polygon; +void TileSetScenesCollectionSource::remove_scene_tile(int p_id) { + ERR_FAIL_COND(!scenes.has(p_id)); + + scenes.erase(p_id); + scenes_ids.erase(p_id); + emit_signal("changed"); } -const Map<Vector2, Ref<OccluderPolygon2D>> &TileSet::autotile_get_light_oclusion_map(int p_id) const { - static Map<Vector2, Ref<OccluderPolygon2D>> dummy; - ERR_FAIL_COND_V(!tile_map.has(p_id), dummy); - return tile_map[p_id].autotile_data.occluder_map; +int TileSetScenesCollectionSource::get_next_scene_tile_id() const { + return next_scene_id; +} + +bool TileSetScenesCollectionSource::_set(const StringName &p_name, const Variant &p_value) { + Vector<String> components = String(p_name).split("/", true, 2); + + if (components.size() >= 2 && components[0] == "scenes" && components[1].is_valid_int()) { + int scene_id = components[1].to_int(); + if (components.size() >= 3 && components[2] == "scene") { + if (has_scene_tile_id(scene_id)) { + set_scene_tile_scene(scene_id, p_value); + } else { + create_scene_tile(p_value, scene_id); + } + return true; + } else if (components.size() >= 3 && components[2] == "display_placeholder") { + if (!has_scene_tile_id(scene_id)) { + create_scene_tile(p_value, scene_id); + } + + return true; + } + } + + return false; } -void TileSet::autotile_set_navigation_polygon(int p_id, const Ref<NavigationPolygon> &p_navigation_polygon, const Vector2 &p_coord) { - ERR_FAIL_COND(!tile_map.has(p_id)); - if (p_navigation_polygon.is_null()) { - if (tile_map[p_id].autotile_data.navpoly_map.has(p_coord)) { - tile_map[p_id].autotile_data.navpoly_map.erase(p_coord); +bool TileSetScenesCollectionSource::_get(const StringName &p_name, Variant &r_ret) const { + Vector<String> components = String(p_name).split("/", true, 2); + + if (components.size() >= 2 && components[0] == "scenes" && components[1].is_valid_int() && scenes.has(components[1].to_int())) { + if (components.size() >= 3 && components[2] == "scene") { + r_ret = scenes[components[1].to_int()].scene; + return true; + } else if (components.size() >= 3 && components[2] == "display_placeholder") { + r_ret = scenes[components[1].to_int()].scene; + return true; } - } else { - tile_map[p_id].autotile_data.navpoly_map[p_coord] = p_navigation_polygon; } + + return false; +} + +void TileSetScenesCollectionSource::_get_property_list(List<PropertyInfo> *p_list) const { + for (int i = 0; i < scenes_ids.size(); i++) { + p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("scenes/%d/scene", scenes_ids[i]), PROPERTY_HINT_RESOURCE_TYPE, "TileSetScenesCollectionSource")); + + PropertyInfo property_info = PropertyInfo(Variant::BOOL, vformat("scenes/%d/display_placeholder", scenes_ids[i])); + if (scenes[scenes_ids[i]].display_placeholder == false) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + } +} + +void TileSetScenesCollectionSource::_bind_methods() { + // Base tiles + ClassDB::bind_method(D_METHOD("get_tiles_count"), &TileSetScenesCollectionSource::get_tiles_count); + ClassDB::bind_method(D_METHOD("get_tile_id", "index"), &TileSetScenesCollectionSource::get_tile_id); + ClassDB::bind_method(D_METHOD("has_tile", "atlas_coords"), &TileSetScenesCollectionSource::has_tile); + + // Alternative tiles + ClassDB::bind_method(D_METHOD("get_alternative_tiles_count", "atlas_coords"), &TileSetScenesCollectionSource::get_alternative_tiles_count); + ClassDB::bind_method(D_METHOD("get_alternative_tile_id", "atlas_coords", "index"), &TileSetScenesCollectionSource::get_alternative_tile_id); + ClassDB::bind_method(D_METHOD("has_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetScenesCollectionSource::has_alternative_tile); + + ClassDB::bind_method(D_METHOD("get_scene_tiles_count"), &TileSetScenesCollectionSource::get_scene_tiles_count); + ClassDB::bind_method(D_METHOD("get_scene_tile_id", "index"), &TileSetScenesCollectionSource::get_scene_tile_id); + ClassDB::bind_method(D_METHOD("has_scene_tile_id", "id"), &TileSetScenesCollectionSource::has_scene_tile_id); + ClassDB::bind_method(D_METHOD("create_scene_tile", "packed_scene", "id_override"), &TileSetScenesCollectionSource::create_scene_tile, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("set_scene_tile_id", "id", "new_id"), &TileSetScenesCollectionSource::set_scene_tile_id); + ClassDB::bind_method(D_METHOD("set_scene_tile_scene", "id", "packed_scene"), &TileSetScenesCollectionSource::set_scene_tile_scene); + ClassDB::bind_method(D_METHOD("get_scene_tile_scene", "id"), &TileSetScenesCollectionSource::get_scene_tile_scene); + ClassDB::bind_method(D_METHOD("set_scene_tile_display_placeholder", "id", "display_placeholder"), &TileSetScenesCollectionSource::set_scene_tile_display_placeholder); + ClassDB::bind_method(D_METHOD("get_scene_tile_display_placeholder", "id"), &TileSetScenesCollectionSource::get_scene_tile_display_placeholder); + ClassDB::bind_method(D_METHOD("remove_scene_tile", "id"), &TileSetScenesCollectionSource::remove_scene_tile); + ClassDB::bind_method(D_METHOD("get_next_scene_tile_id"), &TileSetScenesCollectionSource::get_next_scene_tile_id); +} + +/////////////////////////////// TileData ////////////////////////////////////// + +void TileData::set_tile_set(const TileSet *p_tile_set) { + tile_set = p_tile_set; + notify_tile_data_properties_should_change(); +} + +void TileData::notify_tile_data_properties_should_change() { + if (!tile_set) { + return; + } + + occluders.resize(tile_set->get_occlusion_layers_count()); + physics.resize(tile_set->get_physics_layers_count()); + for (int bit_index = 0; bit_index < 16; bit_index++) { + if (terrain_set < 0 || terrain_peering_bits[bit_index] >= tile_set->get_terrains_count(terrain_set)) { + terrain_peering_bits[bit_index] = -1; + } + } + navigation.resize(tile_set->get_navigation_layers_count()); + + // Convert custom data to the new type. + custom_data.resize(tile_set->get_custom_data_layers_count()); + for (int i = 0; i < custom_data.size(); i++) { + if (custom_data[i].get_type() != tile_set->get_custom_data_type(i)) { + Variant new_val; + Callable::CallError error; + if (Variant::can_convert(custom_data[i].get_type(), tile_set->get_custom_data_type(i))) { + const Variant *args[] = { &custom_data[i] }; + Variant::construct(tile_set->get_custom_data_type(i), new_val, args, 1, error); + } else { + Variant::construct(tile_set->get_custom_data_type(i), new_val, nullptr, 0, error); + } + custom_data.write[i] = new_val; + } + } + + notify_property_list_changed(); + emit_signal("changed"); +} + +void TileData::reset_state() { + occluders.clear(); + physics.clear(); + navigation.clear(); + custom_data.clear(); +} + +void TileData::set_allow_transform(bool p_allow_transform) { + allow_transform = p_allow_transform; +} + +bool TileData::is_allowing_transform() const { + return allow_transform; +} + +// Rendering +void TileData::set_flip_h(bool p_flip_h) { + ERR_FAIL_COND_MSG(!allow_transform && p_flip_h, "Transform is only allowed for alternative tiles (with its alternative_id != 0)"); + flip_h = p_flip_h; + emit_signal("changed"); +} +bool TileData::get_flip_h() const { + return flip_h; +} + +void TileData::set_flip_v(bool p_flip_v) { + ERR_FAIL_COND_MSG(!allow_transform && p_flip_v, "Transform is only allowed for alternative tiles (with its alternative_id != 0)"); + flip_v = p_flip_v; + emit_signal("changed"); +} + +bool TileData::get_flip_v() const { + return flip_v; +} + +void TileData::set_transpose(bool p_transpose) { + ERR_FAIL_COND_MSG(!allow_transform && p_transpose, "Transform is only allowed for alternative tiles (with its alternative_id != 0)"); + transpose = p_transpose; + emit_signal("changed"); +} +bool TileData::get_transpose() const { + return transpose; +} + +void TileData::set_texture_offset(Vector2i p_texture_offset) { + tex_offset = p_texture_offset; + emit_signal("changed"); +} + +Vector2i TileData::get_texture_offset() const { + return tex_offset; +} + +void TileData::tile_set_material(Ref<ShaderMaterial> p_material) { + material = p_material; + emit_signal("changed"); +} +Ref<ShaderMaterial> TileData::tile_get_material() const { + return material; +} + +void TileData::set_modulate(Color p_modulate) { + modulate = p_modulate; + emit_signal("changed"); +} +Color TileData::get_modulate() const { + return modulate; +} + +void TileData::set_z_index(int p_z_index) { + z_index = p_z_index; + emit_signal("changed"); +} +int TileData::get_z_index() const { + return z_index; +} + +void TileData::set_y_sort_origin(int p_y_sort_origin) { + y_sort_origin = p_y_sort_origin; + emit_signal("changed"); +} +int TileData::get_y_sort_origin() const { + return y_sort_origin; +} + +void TileData::set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_polygon) { + ERR_FAIL_INDEX(p_layer_id, occluders.size()); + occluders.write[p_layer_id] = p_occluder_polygon; + emit_signal("changed"); +} + +Ref<OccluderPolygon2D> TileData::get_occluder(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), Ref<OccluderPolygon2D>()); + return occluders[p_layer_id]; +} + +// Physics +int TileData::get_collision_polygons_count(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0); + return physics[p_layer_id].polygons.size(); +} + +void TileData::set_collision_polygons_count(int p_layer_id, int p_polygons_count) { + ERR_FAIL_INDEX(p_layer_id, physics.size()); + ERR_FAIL_COND(p_polygons_count < 0); + physics.write[p_layer_id].polygons.resize(p_polygons_count); + notify_property_list_changed(); + emit_signal("changed"); +} + +void TileData::add_collision_polygon(int p_layer_id) { + ERR_FAIL_INDEX(p_layer_id, physics.size()); + physics.write[p_layer_id].polygons.push_back(PhysicsLayerTileData::PolygonShapeTileData()); + emit_signal("changed"); +} + +void TileData::remove_collision_polygon(int p_layer_id, int p_polygon_index) { + ERR_FAIL_INDEX(p_layer_id, physics.size()); + ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); + physics.write[p_layer_id].polygons.remove(p_polygon_index); + emit_signal("changed"); } -Ref<NavigationPolygon> TileSet::autotile_get_navigation_polygon(int p_id, const Vector2 &p_coord) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), Ref<NavigationPolygon>()); - if (!tile_map[p_id].autotile_data.navpoly_map.has(p_coord)) { - return Ref<NavigationPolygon>(); +void TileData::set_collision_polygon_points(int p_layer_id, int p_polygon_index, Vector<Vector2> p_polygon) { + ERR_FAIL_INDEX(p_layer_id, physics.size()); + ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); + ERR_FAIL_COND_MSG(p_polygon.size() != 0 && p_polygon.size() < 3, "Invalid polygon. Needs either 0 or more than 3 points."); + + if (p_polygon.is_empty()) { + physics.write[p_layer_id].polygons.write[p_polygon_index].shapes.clear(); } else { - return tile_map[p_id].autotile_data.navpoly_map[p_coord]; + // Decompose into convex shapes. + Vector<Vector<Vector2>> decomp = Geometry2D::decompose_polygon_in_convex(p_polygon); + ERR_FAIL_COND_MSG(decomp.is_empty(), "Could not decompose the polygon into convex shapes."); + + physics.write[p_layer_id].polygons.write[p_polygon_index].shapes.resize(decomp.size()); + for (int i = 0; i < decomp.size(); i++) { + Ref<ConvexPolygonShape2D> shape; + shape.instantiate(); + shape->set_points(decomp[i]); + physics.write[p_layer_id].polygons.write[p_polygon_index].shapes[i] = shape; + } } + physics.write[p_layer_id].polygons.write[p_polygon_index].polygon = p_polygon; + emit_signal("changed"); } -const Map<Vector2, Ref<NavigationPolygon>> &TileSet::autotile_get_navigation_map(int p_id) const { - static Map<Vector2, Ref<NavigationPolygon>> dummy; - ERR_FAIL_COND_V(!tile_map.has(p_id), dummy); - return tile_map[p_id].autotile_data.navpoly_map; +Vector<Vector2> TileData::get_collision_polygon_points(int p_layer_id, int p_polygon_index) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), Vector<Vector2>()); + ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), Vector<Vector2>()); + return physics[p_layer_id].polygons[p_polygon_index].polygon; } -void TileSet::tile_set_occluder_offset(int p_id, const Vector2 &p_offset) { - ERR_FAIL_COND(!tile_map.has(p_id)); - tile_map[p_id].occluder_offset = p_offset; +void TileData::set_collision_polygon_one_way(int p_layer_id, int p_polygon_index, bool p_one_way) { + ERR_FAIL_INDEX(p_layer_id, physics.size()); + ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); + physics.write[p_layer_id].polygons.write[p_polygon_index].one_way = p_one_way; + emit_signal("changed"); } -Vector2 TileSet::tile_get_occluder_offset(int p_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), Vector2()); - return tile_map[p_id].occluder_offset; +bool TileData::is_collision_polygon_one_way(int p_layer_id, int p_polygon_index) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), false); + ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), false); + return physics[p_layer_id].polygons[p_polygon_index].one_way; } -void TileSet::tile_set_shapes(int p_id, const Vector<ShapeData> &p_shapes) { - ERR_FAIL_COND(!tile_map.has(p_id)); - tile_map[p_id].shapes_data = p_shapes; - for (int i = 0; i < p_shapes.size(); i++) { - _decompose_convex_shape(p_shapes[i].shape); +void TileData::set_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index, float p_one_way_margin) { + ERR_FAIL_INDEX(p_layer_id, physics.size()); + ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); + physics.write[p_layer_id].polygons.write[p_polygon_index].one_way_margin = p_one_way_margin; + emit_signal("changed"); +} + +float TileData::get_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0.0); + ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), 0.0); + return physics[p_layer_id].polygons[p_polygon_index].one_way_margin; +} + +int TileData::get_collision_polygon_shapes_count(int p_layer_id, int p_polygon_index) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0); + ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), 0); + return physics[p_layer_id].polygons[p_polygon_index].shapes.size(); +} + +Ref<ConvexPolygonShape2D> TileData::get_collision_polygon_shape(int p_layer_id, int p_polygon_index, int shape_index) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0); + ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), Ref<ConvexPolygonShape2D>()); + ERR_FAIL_INDEX_V(shape_index, (int)physics[p_layer_id].polygons[shape_index].shapes.size(), Ref<ConvexPolygonShape2D>()); + return physics[p_layer_id].polygons[shape_index].shapes[shape_index]; +} + +// Terrain +void TileData::set_terrain_set(int p_terrain_set) { + ERR_FAIL_COND(p_terrain_set < -1); + if (p_terrain_set == terrain_set) { + return; } - emit_changed(); + if (tile_set) { + ERR_FAIL_COND(p_terrain_set >= tile_set->get_terrain_sets_count()); + for (int i = 0; i < 16; i++) { + terrain_peering_bits[i] = -1; + } + } + terrain_set = p_terrain_set; + notify_property_list_changed(); + emit_signal("changed"); } -Vector<TileSet::ShapeData> TileSet::tile_get_shapes(int p_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), Vector<ShapeData>()); +int TileData::get_terrain_set() const { + return terrain_set; +} - return tile_map[p_id].shapes_data; +void TileData::set_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit, int p_terrain_index) { + ERR_FAIL_COND(terrain_set < 0); + ERR_FAIL_COND(p_terrain_index < -1); + if (tile_set) { + ERR_FAIL_COND(p_terrain_index >= tile_set->get_terrains_count(terrain_set)); + ERR_FAIL_COND(!is_valid_peering_bit_terrain(p_peering_bit)); + } + terrain_peering_bits[p_peering_bit] = p_terrain_index; + emit_signal("changed"); } -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; +int TileData::get_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) const { + ERR_FAIL_COND_V(!is_valid_peering_bit_terrain(p_peering_bit), -1); + return terrain_peering_bits[p_peering_bit]; } -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(); +bool TileData::is_valid_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) const { + ERR_FAIL_COND_V(!tile_set, false); + + return tile_set->is_valid_peering_bit_terrain(terrain_set, p_peering_bit); } -void TileSet::_tile_set_shapes(int p_id, const Array &p_shapes) { - ERR_FAIL_COND(!tile_map.has(p_id)); - Vector<ShapeData> shapes_data; - Transform2D default_transform = tile_get_shape_transform(p_id, 0); - bool default_one_way = tile_get_shape_one_way(p_id, 0); - Vector2 default_autotile_coord = Vector2(); - for (int i = 0; i < p_shapes.size(); i++) { - ShapeData s = ShapeData(); +// Navigation +void TileData::set_navigation_polygon(int p_layer_id, Ref<NavigationPolygon> p_navigation_polygon) { + ERR_FAIL_INDEX(p_layer_id, navigation.size()); + navigation.write[p_layer_id] = p_navigation_polygon; + emit_signal("changed"); +} - if (p_shapes[i].get_type() == Variant::OBJECT) { - Ref<Shape2D> shape = p_shapes[i]; - if (shape.is_null()) { - continue; +Ref<NavigationPolygon> TileData::get_navigation_polygon(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, navigation.size(), Ref<NavigationPolygon>()); + return navigation[p_layer_id]; +} + +// Misc +void TileData::set_probability(float p_probability) { + ERR_FAIL_COND(p_probability < 0.0); + probability = p_probability; + emit_signal("changed"); +} +float TileData::get_probability() const { + return probability; +} + +// Custom data +void TileData::set_custom_data(String p_layer_name, Variant p_value) { + ERR_FAIL_COND(!tile_set); + int p_layer_id = tile_set->get_custom_data_layer_by_name(p_layer_name); + ERR_FAIL_COND_MSG(p_layer_id < 0, vformat("TileSet has no layer with name: %s", p_layer_name)); + set_custom_data_by_layer_id(p_layer_id, p_value); +} + +Variant TileData::get_custom_data(String p_layer_name) const { + ERR_FAIL_COND_V(!tile_set, Variant()); + int p_layer_id = tile_set->get_custom_data_layer_by_name(p_layer_name); + ERR_FAIL_COND_V_MSG(p_layer_id < 0, Variant(), vformat("TileSet has no layer with name: %s", p_layer_name)); + return get_custom_data_by_layer_id(p_layer_id); +} + +void TileData::set_custom_data_by_layer_id(int p_layer_id, Variant p_value) { + ERR_FAIL_INDEX(p_layer_id, custom_data.size()); + custom_data.write[p_layer_id] = p_value; + emit_signal("changed"); +} + +Variant TileData::get_custom_data_by_layer_id(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, custom_data.size(), Variant()); + return custom_data[p_layer_id]; +} + +bool TileData::_set(const StringName &p_name, const Variant &p_value) { + Vector<String> components = String(p_name).split("/", true, 2); + + if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { + // Occlusion layers. + int layer_index = components[0].trim_prefix("occlusion_layer_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + if (components[1] == "polygon") { + Ref<OccluderPolygon2D> polygon = p_value; + if (!polygon.is_valid()) { + return false; } - s.shape = shape; - s.shape_transform = default_transform; - s.one_way_collision = default_one_way; - s.autotile_coord = default_autotile_coord; - } else if (p_shapes[i].get_type() == Variant::DICTIONARY) { - Dictionary d = p_shapes[i]; + if (layer_index >= occluders.size()) { + if (tile_set) { + return false; + } else { + occluders.resize(layer_index + 1); + } + } + set_occluder(layer_index, polygon); + return true; + } + } else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { + // Physics layers. + int layer_index = components[0].trim_prefix("physics_layer_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + if (components.size() == 2 && components[1] == "polygons_count") { + if (p_value.get_type() != Variant::INT) { + return false; + } - if (d.has("shape") && d["shape"].get_type() == Variant::OBJECT) { - s.shape = d["shape"]; - _decompose_convex_shape(s.shape); - } else { - continue; + if (layer_index >= physics.size()) { + if (tile_set) { + return false; + } else { + physics.resize(layer_index + 1); + } } + set_collision_polygons_count(layer_index, p_value); + return true; + } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { + int polygon_index = components[1].trim_prefix("polygon_").to_int(); + ERR_FAIL_COND_V(polygon_index < 0, false); + + if (components[2] == "points" || components[2] == "one_way" || components[2] == "one_way_margin") { + if (layer_index >= physics.size()) { + if (tile_set) { + return false; + } else { + physics.resize(layer_index + 1); + } + } - if (d.has("shape_transform") && d["shape_transform"].get_type() == Variant::TRANSFORM2D) { - s.shape_transform = d["shape_transform"]; - } else if (d.has("shape_offset") && d["shape_offset"].get_type() == Variant::VECTOR2) { - s.shape_transform = Transform2D(0, (Vector2)d["shape_offset"]); - } else { - s.shape_transform = default_transform; + if (polygon_index >= physics[layer_index].polygons.size()) { + physics.write[layer_index].polygons.resize(polygon_index + 1); + } + } + if (components[2] == "points") { + Vector<Vector2> polygon = p_value; + set_collision_polygon_points(layer_index, polygon_index, polygon); + return true; + } else if (components[2] == "one_way") { + set_collision_polygon_one_way(layer_index, polygon_index, p_value); + return true; + } else if (components[2] == "one_way_margin") { + set_collision_polygon_one_way_margin(layer_index, polygon_index, p_value); + return true; + } + } + } else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_int()) { + // Navigation layers. + int layer_index = components[0].trim_prefix("navigation_layer_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + if (components[1] == "polygon") { + Ref<NavigationPolygon> polygon = p_value; + if (!polygon.is_valid()) { + return false; } - if (d.has("one_way") && d["one_way"].get_type() == Variant::BOOL) { - s.one_way_collision = d["one_way"]; + if (layer_index >= navigation.size()) { + if (tile_set) { + return false; + } else { + navigation.resize(layer_index + 1); + } + } + set_navigation_polygon(layer_index, polygon); + return true; + } + } else if (components.size() == 2 && components[0] == "terrains_peering_bit") { + // Terrains. + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (components[1] == TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]) { + set_peering_bit_terrain(bit, p_value); + return true; + } + } + return false; + } else if (components.size() == 1 && components[0].begins_with("custom_data_") && components[0].trim_prefix("custom_data_").is_valid_int()) { + // Custom data layers. + int layer_index = components[0].trim_prefix("custom_data_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + + if (layer_index >= custom_data.size()) { + if (tile_set) { + return false; } else { - s.one_way_collision = default_one_way; + custom_data.resize(layer_index + 1); } + } + set_custom_data_by_layer_id(layer_index, p_value); - if (d.has("one_way_margin") && d["one_way_margin"].is_num()) { - s.one_way_collision_margin = d["one_way_margin"]; - } else { - s.one_way_collision_margin = 1.0; + return true; + } + + return false; +} + +bool TileData::_get(const StringName &p_name, Variant &r_ret) const { + Vector<String> components = String(p_name).split("/", true, 2); + + if (tile_set) { + if (components.size() == 2 && components[0].begins_with("occlusion_layer") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { + // Occlusion layers. + int layer_index = components[0].trim_prefix("occlusion_layer_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + if (layer_index >= occluders.size()) { + return false; } + if (components[1] == "polygon") { + r_ret = get_occluder(layer_index); + return true; + } + } else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { + // Physics layers. + int layer_index = components[0].trim_prefix("physics_layer_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + if (layer_index >= physics.size()) { + return false; + } + if (components.size() == 2 && components[1] == "polygons_count") { + r_ret = get_collision_polygons_count(layer_index); + return true; + } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { + int polygon_index = components[1].trim_prefix("polygon_").to_int(); + ERR_FAIL_COND_V(polygon_index < 0, false); + if (polygon_index >= physics[layer_index].polygons.size()) { + return false; + } + if (components[2] == "points") { + r_ret = get_collision_polygon_points(layer_index, polygon_index); + return true; + } else if (components[2] == "one_way") { + r_ret = is_collision_polygon_one_way(layer_index, polygon_index); + return true; + } else if (components[2] == "one_way_margin") { + r_ret = get_collision_polygon_one_way_margin(layer_index, polygon_index); + return true; + } + } + } else if (components.size() == 2 && components[0] == "terrains_peering_bit") { + // Terrains. + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + if (components[1] == TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]) { + r_ret = terrain_peering_bits[i]; + return true; + } + } + return false; + } else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_int()) { + // Occlusion layers. + int layer_index = components[0].trim_prefix("navigation_layer_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + if (layer_index >= navigation.size()) { + return false; + } + if (components[1] == "polygon") { + r_ret = get_navigation_polygon(layer_index); + return true; + } + } else if (components.size() == 1 && components[0].begins_with("custom_data_") && components[0].trim_prefix("custom_data_").is_valid_int()) { + // Custom data layers. + int layer_index = components[0].trim_prefix("custom_data_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + if (layer_index >= custom_data.size()) { + return false; + } + r_ret = get_custom_data_by_layer_id(layer_index); + return true; + } + } - if (d.has("autotile_coord") && d["autotile_coord"].get_type() == Variant::VECTOR2) { - s.autotile_coord = d["autotile_coord"]; - } else { - s.autotile_coord = default_autotile_coord; + return false; +} + +void TileData::_get_property_list(List<PropertyInfo> *p_list) const { + PropertyInfo property_info; + // Add the groups manually. + if (tile_set) { + // Occlusion layers. + p_list->push_back(PropertyInfo(Variant::NIL, "Rendering", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < occluders.size(); i++) { + // occlusion_layer_%d/polygon + property_info = PropertyInfo(Variant::OBJECT, vformat("occlusion_layer_%d/polygon", i), PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D", PROPERTY_USAGE_DEFAULT); + if (!occluders[i].is_valid()) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; } + p_list->push_back(property_info); + } - } else { - ERR_CONTINUE_MSG(true, "Expected an array of objects or dictionaries for tile_set_shapes."); + // Physics layers. + p_list->push_back(PropertyInfo(Variant::NIL, "Physics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < physics.size(); i++) { + p_list->push_back(PropertyInfo(Variant::INT, vformat("physics_layer_%d/polygons_count", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); + + for (int j = 0; j < physics[i].polygons.size(); j++) { + // physics_layer_%d/points + property_info = PropertyInfo(Variant::ARRAY, vformat("physics_layer_%d/polygon_%d/points", i, j), PROPERTY_HINT_ARRAY_TYPE, "Vector2", PROPERTY_USAGE_DEFAULT); + if (physics[i].polygons[j].polygon.is_empty()) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + + // physics_layer_%d/polygon_%d/one_way + property_info = PropertyInfo(Variant::BOOL, vformat("physics_layer_%d/polygon_%d/one_way", i, j)); + if (physics[i].polygons[j].one_way == false) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + + // physics_layer_%d/polygon_%d/one_way_margin + property_info = PropertyInfo(Variant::FLOAT, vformat("physics_layer_%d/polygon_%d/one_way_margin", i, j)); + if (physics[i].polygons[j].one_way_margin == 1.0) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + } + } + + // Terrain data + if (terrain_set >= 0) { + p_list->push_back(PropertyInfo(Variant::NIL, "Terrains", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (is_valid_peering_bit_terrain(bit)) { + property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i])); + if (get_peering_bit_terrain(bit) == -1) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + } + } } - shapes_data.push_back(s); + // Navigation layers. + p_list->push_back(PropertyInfo(Variant::NIL, "Navigation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < navigation.size(); i++) { + property_info = PropertyInfo(Variant::OBJECT, vformat("navigation_layer_%d/polygon", i), PROPERTY_HINT_RESOURCE_TYPE, "NavigationPolygon", PROPERTY_USAGE_DEFAULT); + if (!navigation[i].is_valid()) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + } + + // Custom data layers. + p_list->push_back(PropertyInfo(Variant::NIL, "Custom data", PROPERTY_HINT_NONE, "custom_data_", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < custom_data.size(); i++) { + Variant default_val; + Callable::CallError error; + Variant::construct(custom_data[i].get_type(), default_val, nullptr, 0, error); + property_info = PropertyInfo(tile_set->get_custom_data_type(i), vformat("custom_data_%d", i), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT); + if (custom_data[i] == default_val) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + } } +} - tile_map[p_id].shapes_data = shapes_data; - emit_changed(); +void TileData::_bind_methods() { + // Rendering. + ClassDB::bind_method(D_METHOD("set_flip_h", "flip_h"), &TileData::set_flip_h); + ClassDB::bind_method(D_METHOD("get_flip_h"), &TileData::get_flip_h); + ClassDB::bind_method(D_METHOD("set_flip_v", "flip_v"), &TileData::set_flip_v); + ClassDB::bind_method(D_METHOD("get_flip_v"), &TileData::get_flip_v); + ClassDB::bind_method(D_METHOD("set_transpose", "transpose"), &TileData::set_transpose); + ClassDB::bind_method(D_METHOD("get_transpose"), &TileData::get_transpose); + ClassDB::bind_method(D_METHOD("tile_set_material", "material"), &TileData::tile_set_material); + ClassDB::bind_method(D_METHOD("tile_get_material"), &TileData::tile_get_material); + ClassDB::bind_method(D_METHOD("set_texture_offset", "texture_offset"), &TileData::set_texture_offset); + ClassDB::bind_method(D_METHOD("get_texture_offset"), &TileData::get_texture_offset); + ClassDB::bind_method(D_METHOD("set_modulate", "modulate"), &TileData::set_modulate); + ClassDB::bind_method(D_METHOD("get_modulate"), &TileData::get_modulate); + ClassDB::bind_method(D_METHOD("set_z_index", "z_index"), &TileData::set_z_index); + ClassDB::bind_method(D_METHOD("get_z_index"), &TileData::get_z_index); + ClassDB::bind_method(D_METHOD("set_y_sort_origin", "y_sort_origin"), &TileData::set_y_sort_origin); + ClassDB::bind_method(D_METHOD("get_y_sort_origin"), &TileData::get_y_sort_origin); + + ClassDB::bind_method(D_METHOD("set_occluder", "layer_id", "occluder_polygon"), &TileData::set_occluder); + ClassDB::bind_method(D_METHOD("get_occluder", "layer_id"), &TileData::get_occluder); + + // Physics. + ClassDB::bind_method(D_METHOD("get_collision_polygons_count", "layer_id"), &TileData::get_collision_polygons_count); + ClassDB::bind_method(D_METHOD("set_collision_polygons_count", "layer_id", "polygons_count"), &TileData::set_collision_polygons_count); + ClassDB::bind_method(D_METHOD("add_collision_polygon", "layer_id"), &TileData::add_collision_polygon); + ClassDB::bind_method(D_METHOD("remove_collision_polygon", "layer_id", "polygon_index"), &TileData::remove_collision_polygon); + ClassDB::bind_method(D_METHOD("set_collision_polygon_points", "layer_id", "polygon_index", "polygon"), &TileData::set_collision_polygon_points); + ClassDB::bind_method(D_METHOD("get_collision_polygon_points", "layer_id", "polygon_index"), &TileData::get_collision_polygon_points); + ClassDB::bind_method(D_METHOD("set_collision_polygon_one_way", "layer_id", "polygon_index", "one_way"), &TileData::set_collision_polygon_one_way); + ClassDB::bind_method(D_METHOD("is_collision_polygon_one_way", "layer_id", "polygon_index"), &TileData::is_collision_polygon_one_way); + ClassDB::bind_method(D_METHOD("set_collision_polygon_one_way_margin", "layer_id", "polygon_index", "one_way_margin"), &TileData::set_collision_polygon_one_way_margin); + ClassDB::bind_method(D_METHOD("get_collision_polygon_one_way_margin", "layer_id", "polygon_index"), &TileData::get_collision_polygon_one_way_margin); + + // Terrain + ClassDB::bind_method(D_METHOD("set_terrain_set", "terrain_set"), &TileData::set_terrain_set); + ClassDB::bind_method(D_METHOD("get_terrain_set"), &TileData::get_terrain_set); + ClassDB::bind_method(D_METHOD("set_peering_bit_terrain", "peering_bit", "terrain"), &TileData::set_peering_bit_terrain); + ClassDB::bind_method(D_METHOD("get_peering_bit_terrain", "peering_bit"), &TileData::get_peering_bit_terrain); + + // Navigation + ClassDB::bind_method(D_METHOD("set_navigation_polygon", "layer_id", "navigation_polygon"), &TileData::set_navigation_polygon); + ClassDB::bind_method(D_METHOD("get_navigation_polygon", "layer_id"), &TileData::get_navigation_polygon); + + // Misc. + ClassDB::bind_method(D_METHOD("set_probability", "probability"), &TileData::set_probability); + ClassDB::bind_method(D_METHOD("get_probability"), &TileData::get_probability); + + // Custom data. + ClassDB::bind_method(D_METHOD("set_custom_data", "layer_name", "value"), &TileData::set_custom_data); + ClassDB::bind_method(D_METHOD("get_custom_data", "layer_name"), &TileData::get_custom_data); + ClassDB::bind_method(D_METHOD("set_custom_data_by_layer_id", "layer_id", "value"), &TileData::set_custom_data_by_layer_id); + ClassDB::bind_method(D_METHOD("get_custom_data_by_layer_id", "layer_id"), &TileData::get_custom_data_by_layer_id); + + ADD_GROUP("Rendering", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h"), "set_flip_h", "get_flip_h"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_v"), "set_flip_v", "get_flip_v"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "transpose"), "set_transpose", "get_transpose"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "texture_offset"), "set_texture_offset", "get_texture_offset"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "modulate"), "set_modulate", "get_modulate"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "z_index"), "set_z_index", "get_z_index"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "y_sort_origin"), "set_y_sort_origin", "get_y_sort_origin"); + + ADD_GROUP("Terrains", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "terrain_set"), "set_terrain_set", "get_terrain_set"); + + ADD_GROUP("Miscellaneous", ""); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "probability"), "set_probability", "get_probability"); + + ADD_SIGNAL(MethodInfo("changed")); } +/////////////////////////////// TileSetPluginAtlasRendering ////////////////////////////////////// + +void TileSetPluginAtlasRendering::tilemap_notification(TileMap *p_tile_map, int p_what) { + switch (p_what) { + case CanvasItem::NOTIFICATION_VISIBILITY_CHANGED: { + bool visible = p_tile_map->is_visible_in_tree(); + for (Map<Vector2i, TileMapQuadrant>::Element *E_quadrant = p_tile_map->get_quadrant_map().front(); E_quadrant; E_quadrant = E_quadrant->next()) { + TileMapQuadrant &q = E_quadrant->get(); + + // Update occluders transform. + for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E_cell = q.world_to_map.front(); E_cell; E_cell = E_cell->next()) { + Transform2D xform; + xform.set_origin(E_cell->key()); + for (List<RID>::Element *E_occluder_id = q.occluders.front(); E_occluder_id; E_occluder_id = E_occluder_id->next()) { + RS::get_singleton()->canvas_light_occluder_set_enabled(E_occluder_id->get(), visible); + } + } + } + } break; + case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: { + if (!p_tile_map->is_inside_tree()) { + return; + } -Array TileSet::_tile_get_shapes(int p_id) const { - ERR_FAIL_COND_V(!tile_map.has(p_id), Array()); - Array arr; + for (Map<Vector2i, TileMapQuadrant>::Element *E_quadrant = p_tile_map->get_quadrant_map().front(); E_quadrant; E_quadrant = E_quadrant->next()) { + TileMapQuadrant &q = E_quadrant->get(); - Vector<ShapeData> data = tile_map[p_id].shapes_data; - for (int i = 0; i < data.size(); i++) { - Dictionary shape_data; - shape_data["shape"] = data[i].shape; - shape_data["shape_transform"] = data[i].shape_transform; - shape_data["one_way"] = data[i].one_way_collision; - shape_data["one_way_margin"] = data[i].one_way_collision_margin; - shape_data["autotile_coord"] = data[i].autotile_coord; - arr.push_back(shape_data); + // Update occluders transform. + for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E_cell = q.world_to_map.front(); E_cell; E_cell = E_cell->next()) { + Transform2D xform; + xform.set_origin(E_cell->key()); + for (List<RID>::Element *E_occluder_id = q.occluders.front(); E_occluder_id; E_occluder_id = E_occluder_id->next()) { + RS::get_singleton()->canvas_light_occluder_set_transform(E_occluder_id->get(), p_tile_map->get_global_transform() * xform); + } + } + } + } break; + case CanvasItem::NOTIFICATION_DRAW: { + Ref<TileSet> tile_set = p_tile_map->get_tileset(); + if (tile_set.is_valid() || p_tile_map->is_y_sort_enabled()) { + RenderingServer::get_singleton()->canvas_item_set_sort_children_by_y(p_tile_map->get_canvas_item(), tile_set->is_y_sorting() || p_tile_map->is_y_sort_enabled()); + } + } break; } +} + +void TileSetPluginAtlasRendering::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation) { + ERR_FAIL_COND(!p_tile_set.is_valid()); + ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id)); + ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords)); + ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_alternative_tile(p_atlas_coords, p_alternative_tile)); + + TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + // Get the texture. + Ref<Texture2D> tex = atlas_source->get_texture(); + if (!tex.is_valid()) { + return; + } + + // Check if we are in the texture, return otherwise. + Vector2i grid_size = atlas_source->get_atlas_grid_size(); + if (p_atlas_coords.x >= grid_size.x || p_atlas_coords.y >= grid_size.y) { + return; + } + + // Get tile data. + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile)); + + // Compute the offset + Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords); + Vector2i tile_offset = atlas_source->get_tile_effective_texture_offset(p_atlas_coords, p_alternative_tile); + + // Compute the destination rectangle in the CanvasItem. + Rect2 dest_rect; + dest_rect.size = source_rect.size; + dest_rect.size.x += fp_adjust; + dest_rect.size.y += fp_adjust; + + bool transpose = tile_data->get_transpose(); + if (transpose) { + dest_rect.position = (p_position - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); + } else { + dest_rect.position = (p_position - dest_rect.size / 2 - tile_offset); + } + + if (tile_data->get_flip_h()) { + dest_rect.size.x = -dest_rect.size.x; + } + + if (tile_data->get_flip_v()) { + dest_rect.size.y = -dest_rect.size.y; + } - return arr; + // Get the tile modulation. + Color modulate = tile_data->get_modulate(); + modulate = Color(modulate.r * p_modulation.r, modulate.g * p_modulation.g, modulate.b * p_modulation.b, modulate.a * p_modulation.a); + + // Draw the tile. + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + } } -Array TileSet::_get_tiles_ids() const { - Array arr; +void TileSetPluginAtlasRendering::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + ERR_FAIL_COND(!p_tile_map); + ERR_FAIL_COND(!p_tile_map->is_inside_tree()); + Ref<TileSet> tile_set = p_tile_map->get_tileset(); + ERR_FAIL_COND(!tile_set.is_valid()); + + bool visible = p_tile_map->is_visible_in_tree(); + + SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); + while (q_list_element) { + TileMapQuadrant &q = *q_list_element->self(); + + RenderingServer *rs = RenderingServer::get_singleton(); + + // Free the canvas items. + for (List<RID>::Element *E = q.canvas_items.front(); E; E = E->next()) { + rs->free(E->get()); + } + q.canvas_items.clear(); + + // Free the occluders. + for (List<RID>::Element *E = q.occluders.front(); E; E = E->next()) { + rs->free(E->get()); + } + q.occluders.clear(); + + // Those allow to group cell per material or z-index. + Ref<ShaderMaterial> prev_material; + int prev_z_index = 0; + RID prev_canvas_item; + + // Iterate over the cells of the quadrant. + for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E_cell = q.world_to_map.front(); E_cell; E_cell = E_cell->next()) { + TileMapCell c = p_tile_map->get_cell(E_cell->value()); + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + // Get the tile data. + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + Ref<ShaderMaterial> mat = tile_data->tile_get_material(); + int z_index = tile_data->get_z_index(); + + // Quandrant pos. + Vector2 position = p_tile_map->map_to_world(q.coords * p_tile_map->get_effective_quadrant_size()); + if (tile_set->is_y_sorting()) { + // When Y-sorting, the quandrant size is sure to be 1, we can thus offset the CanvasItem. + position.y += tile_data->get_y_sort_origin(); + } + + // --- CanvasItems --- + // Create two canvas items, for rendering and debug. + RID canvas_item; + + // Check if the material or the z_index changed. + if (prev_canvas_item == RID() || prev_material != mat || prev_z_index != z_index) { + // If so, create a new CanvasItem. + canvas_item = rs->canvas_item_create(); + if (mat.is_valid()) { + rs->canvas_item_set_material(canvas_item, mat->get_rid()); + } + rs->canvas_item_set_parent(canvas_item, p_tile_map->get_canvas_item()); + rs->canvas_item_set_use_parent_material(canvas_item, p_tile_map->get_use_parent_material() || p_tile_map->get_material().is_valid()); + + Transform2D xform; + xform.set_origin(position); + rs->canvas_item_set_transform(canvas_item, xform); + + rs->canvas_item_set_light_mask(canvas_item, p_tile_map->get_light_mask()); + rs->canvas_item_set_z_index(canvas_item, z_index); + + rs->canvas_item_set_default_texture_filter(canvas_item, RS::CanvasItemTextureFilter(p_tile_map->CanvasItem::get_texture_filter())); + rs->canvas_item_set_default_texture_repeat(canvas_item, RS::CanvasItemTextureRepeat(p_tile_map->CanvasItem::get_texture_repeat())); + + q.canvas_items.push_back(canvas_item); + + prev_canvas_item = canvas_item; + prev_material = mat; + prev_z_index = z_index; + + } else { + // Keep the same canvas_item to draw on. + canvas_item = prev_canvas_item; + } + + // Drawing the tile in the canvas item. + draw_tile(canvas_item, E_cell->key() - position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, p_tile_map->get_self_modulate()); + + // --- Occluders --- + for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) { + Transform2D xform; + xform.set_origin(E_cell->key()); + if (tile_data->get_occluder(i).is_valid()) { + RID occluder_id = rs->canvas_light_occluder_create(); + rs->canvas_light_occluder_set_enabled(occluder_id, visible); + rs->canvas_light_occluder_set_transform(occluder_id, p_tile_map->get_global_transform() * xform); + rs->canvas_light_occluder_set_polygon(occluder_id, tile_data->get_occluder(i)->get_rid()); + rs->canvas_light_occluder_attach_to_canvas(occluder_id, p_tile_map->get_canvas()); + rs->canvas_light_occluder_set_light_mask(occluder_id, tile_set->get_occlusion_layer_light_mask(i)); + q.occluders.push_back(occluder_id); + } + } + } + } + } - for (Map<int, TileData>::Element *E = tile_map.front(); E; E = E->next()) { - arr.push_back(E->key()); + quadrant_order_dirty = true; + q_list_element = q_list_element->next(); } - return arr; + // Reset the drawing indices + if (quadrant_order_dirty) { + int index = -(int64_t)0x80000000; //always must be drawn below children. + + // Sort the quadrants coords per world coordinates + Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator> world_to_map; + Map<Vector2i, TileMapQuadrant> quadrant_map = p_tile_map->get_quadrant_map(); + for (Map<Vector2i, TileMapQuadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { + world_to_map[p_tile_map->map_to_world(E->key())] = E->key(); + } + + // Sort the quadrants + for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E = world_to_map.front(); E; E = E->next()) { + TileMapQuadrant &q = quadrant_map[E->value()]; + for (List<RID>::Element *F = q.canvas_items.front(); F; F = F->next()) { + RS::get_singleton()->canvas_item_set_draw_index(F->get(), index++); + } + } + + quadrant_order_dirty = false; + } } -void TileSet::_decompose_convex_shape(Ref<Shape2D> p_shape) { - if (Engine::get_singleton()->is_editor_hint()) { - return; +void TileSetPluginAtlasRendering::create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { + Ref<TileSet> tile_set = p_tile_map->get_tileset(); + ERR_FAIL_COND(!tile_set.is_valid()); + + quadrant_order_dirty = true; +} + +void TileSetPluginAtlasRendering::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { + // Free the canvas items. + for (List<RID>::Element *E = p_quadrant->canvas_items.front(); E; E = E->next()) { + RenderingServer::get_singleton()->free(E->get()); + } + p_quadrant->canvas_items.clear(); + + // Free the occluders. + for (List<RID>::Element *E = p_quadrant->occluders.front(); E; E = E->next()) { + RenderingServer::get_singleton()->free(E->get()); } - Ref<ConvexPolygonShape2D> convex = p_shape; - if (!convex.is_valid()) { + p_quadrant->occluders.clear(); +} + +void TileSetPluginAtlasRendering::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { + Ref<TileSet> tile_set = p_tile_map->get_tileset(); + ERR_FAIL_COND(!tile_set.is_valid()); + + if (!Engine::get_singleton()->is_editor_hint()) { return; } - Vector<Vector<Vector2>> decomp = Geometry2D::decompose_polygon_in_convex(convex->get_points()); - if (decomp.size() > 1) { - Array sub_shapes; - for (int i = 0; i < decomp.size(); i++) { - Ref<ConvexPolygonShape2D> _convex = memnew(ConvexPolygonShape2D); - _convex->set_points(decomp[i]); - sub_shapes.append(_convex); + + // Draw a placeholder for scenes needing one. + RenderingServer *rs = RenderingServer::get_singleton(); + Vector2 quadrant_pos = p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size()); + for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { + const TileMapCell &c = p_tile_map->get_cell(E_cell->get()); + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + Vector2i grid_size = atlas_source->get_atlas_grid_size(); + if (!atlas_source->get_texture().is_valid() || c.get_atlas_coords().x >= grid_size.x || c.get_atlas_coords().y >= grid_size.y) { + // Generate a random color from the hashed values of the tiles. + Array to_hash; + to_hash.push_back(c.source_id); + to_hash.push_back(c.get_atlas_coords()); + to_hash.push_back(c.alternative_tile); + uint32_t hash = RandomPCG(to_hash.hash()).rand(); + + Color color; + color = color.from_hsv( + (float)((hash >> 24) & 0xFF) / 256.0, + Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0), + Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0), + 0.8); + + // Draw a placeholder tile. + Transform2D xform; + xform.set_origin(p_tile_map->map_to_world(E_cell->get()) - quadrant_pos); + rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform); + rs->canvas_item_add_circle(p_quadrant->debug_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color); + } + } } - convex->set_meta("decomposed", sub_shapes); - } else { - convex->set_meta("decomposed", Variant()); } } -void TileSet::get_tile_list(List<int> *p_tiles) const { - for (Map<int, TileData>::Element *E = tile_map.front(); E; E = E->next()) { - p_tiles->push_back(E->key()); +/////////////////////////////// TileSetPluginAtlasPhysics ////////////////////////////////////// + +void TileSetPluginAtlasPhysics::tilemap_notification(TileMap *p_tile_map, int p_what) { + switch (p_what) { + case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: { + // Update the bodies transforms. + if (p_tile_map->is_inside_tree()) { + Map<Vector2i, TileMapQuadrant> quadrant_map = p_tile_map->get_quadrant_map(); + Transform2D global_transform = p_tile_map->get_global_transform(); + + for (Map<Vector2i, TileMapQuadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { + TileMapQuadrant &q = E->get(); + + Transform2D xform; + xform.set_origin(p_tile_map->map_to_world(E->key() * p_tile_map->get_effective_quadrant_size())); + xform = global_transform * xform; + + for (int body_index = 0; body_index < q.bodies.size(); body_index++) { + PhysicsServer2D::get_singleton()->body_set_state(q.bodies[body_index], PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + } + } + } + } break; } } -bool TileSet::has_tile(int p_id) const { - return tile_map.has(p_id); -} +void TileSetPluginAtlasPhysics::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + ERR_FAIL_COND(!p_tile_map); + ERR_FAIL_COND(!p_tile_map->is_inside_tree()); + Ref<TileSet> tile_set = p_tile_map->get_tileset(); + ERR_FAIL_COND(!tile_set.is_valid()); -bool TileSet::is_tile_bound(int p_drawn_id, int p_neighbor_id) { - if (p_drawn_id == p_neighbor_id) { - return true; - } else if (get_script_instance() != nullptr) { - if (get_script_instance()->has_method("_is_tile_bound")) { - Variant ret = get_script_instance()->call("_is_tile_bound", p_drawn_id, p_neighbor_id); - if (ret.get_type() == Variant::BOOL) { - return ret; + Transform2D global_transform = p_tile_map->get_global_transform(); + PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); + + SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); + while (q_list_element) { + TileMapQuadrant &q = *q_list_element->self(); + + Vector2 quadrant_pos = p_tile_map->map_to_world(q.coords * p_tile_map->get_effective_quadrant_size()); + + // Clear shapes. + for (int body_index = 0; body_index < q.bodies.size(); body_index++) { + ps->body_clear_shapes(q.bodies[body_index]); + + // Position the bodies. + Transform2D xform; + xform.set_origin(quadrant_pos); + xform = global_transform * xform; + ps->body_set_state(q.bodies[body_index], PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + } + + for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) { + TileMapCell c = p_tile_map->get_cell(E_cell->get()); + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + + for (int body_index = 0; body_index < q.bodies.size(); body_index++) { + // Add the shapes again. + for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(body_index); polygon_index++) { + bool one_way_collision = tile_data->is_collision_polygon_one_way(body_index, polygon_index); + float one_way_collision_margin = tile_data->get_collision_polygon_one_way_margin(body_index, polygon_index); + + int shapes_count = tile_data->get_collision_polygon_shapes_count(body_index, polygon_index); + for (int shape_index = 0; shape_index < shapes_count; shape_index++) { + Transform2D xform = Transform2D(); + xform.set_origin(p_tile_map->map_to_world(E_cell->get()) - quadrant_pos); + + // Add decomposed convex shapes. + Ref<ConvexPolygonShape2D> shape = tile_data->get_collision_polygon_shape(body_index, polygon_index, shape_index); + ps->body_add_shape(q.bodies[body_index], shape->get_rid(), xform); + ps->body_set_shape_metadata(q.bodies[body_index], shape_index, E_cell->get()); + ps->body_set_shape_as_one_way_collision(q.bodies[body_index], shape_index, one_way_collision, one_way_collision_margin); + } + } + } + } } } + + q_list_element = q_list_element->next(); } - return false; } -void TileSet::remove_tile(int p_id) { - ERR_FAIL_COND(!tile_map.has(p_id)); - tile_map.erase(p_id); - notify_property_list_changed(); - emit_changed(); +void TileSetPluginAtlasPhysics::create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { + Ref<TileSet> tile_set = p_tile_map->get_tileset(); + ERR_FAIL_COND(!tile_set.is_valid()); + + //Get the TileMap's gobla transform. + Transform2D global_transform; + if (p_tile_map->is_inside_tree()) { + global_transform = p_tile_map->get_global_transform(); + } + + // Clear all bodies. + p_quadrant->bodies.clear(); + + // Create the body and set its parameters. + for (int layer_index = 0; layer_index < tile_set->get_physics_layers_count(); layer_index++) { + RID body = PhysicsServer2D::get_singleton()->body_create(); + PhysicsServer2D::get_singleton()->body_set_mode(body, PhysicsServer2D::BODY_MODE_STATIC); + + PhysicsServer2D::get_singleton()->body_attach_object_instance_id(body, p_tile_map->get_instance_id()); + PhysicsServer2D::get_singleton()->body_set_collision_layer(body, tile_set->get_physics_layer_collision_layer(layer_index)); + PhysicsServer2D::get_singleton()->body_set_collision_mask(body, tile_set->get_physics_layer_collision_mask(layer_index)); + + Ref<PhysicsMaterial> physics_material = tile_set->get_physics_layer_physics_material(layer_index); + if (!physics_material.is_valid()) { + PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, 0); + PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, 1); + } else { + PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material->computed_bounce()); + PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, physics_material->computed_friction()); + } + + if (p_tile_map->is_inside_tree()) { + RID space = p_tile_map->get_world_2d()->get_space(); + PhysicsServer2D::get_singleton()->body_set_space(body, space); + + Transform2D xform; + xform.set_origin(p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size())); + xform = global_transform * xform; + PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + } + + p_quadrant->bodies.push_back(body); + } } -int TileSet::get_last_unused_tile_id() const { - if (tile_map.size()) { - return tile_map.back()->key() + 1; - } else { - return 0; +void TileSetPluginAtlasPhysics::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { + // Remove a quadrant. + for (int body_index = 0; body_index < p_quadrant->bodies.size(); body_index++) { + PhysicsServer2D::get_singleton()->free(p_quadrant->bodies[body_index]); } + p_quadrant->bodies.clear(); } -int TileSet::find_tile_by_name(const String &p_name) const { - for (Map<int, TileData>::Element *E = tile_map.front(); E; E = E->next()) { - if (p_name == E->get().name) { - return E->key(); +void TileSetPluginAtlasPhysics::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { + // Draw the debug collision shapes. + Ref<TileSet> tile_set = p_tile_map->get_tileset(); + ERR_FAIL_COND(!tile_set.is_valid()); + + if (!p_tile_map->get_tree()) { + return; + } + + bool show_collision = false; + switch (p_tile_map->get_collision_visibility_mode()) { + case TileMap::VISIBILITY_MODE_DEFAULT: + show_collision = !Engine::get_singleton()->is_editor_hint() && (p_tile_map->get_tree() && p_tile_map->get_tree()->is_debugging_navigation_hint()); + break; + case TileMap::VISIBILITY_MODE_FORCE_HIDE: + show_collision = false; + break; + case TileMap::VISIBILITY_MODE_FORCE_SHOW: + show_collision = true; + break; + } + if (!show_collision) { + return; + } + + RenderingServer *rs = RenderingServer::get_singleton(); + + Vector2 quadrant_pos = p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size()); + + Color debug_collision_color = p_tile_map->get_tree()->get_debug_collisions_color(); + for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { + TileMapCell c = p_tile_map->get_cell(E_cell->get()); + + Transform2D xform; + xform.set_origin(p_tile_map->map_to_world(E_cell->get()) - quadrant_pos); + rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform); + + if (tile_set->has_source(c.source_id)) { + TileSetSource *source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + + for (int body_index = 0; body_index < p_quadrant->bodies.size(); body_index++) { + for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(body_index); polygon_index++) { + // Draw the debug polygon. + Vector<Vector2> polygon = tile_data->get_collision_polygon_points(body_index, polygon_index); + if (polygon.size() >= 3) { + Vector<Color> color; + color.push_back(debug_collision_color); + rs->canvas_item_add_polygon(p_quadrant->debug_canvas_item, polygon, color); + } + } + } + } } + rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, Transform2D()); + } +}; + +/////////////////////////////// TileSetPluginAtlasNavigation ////////////////////////////////////// + +void TileSetPluginAtlasNavigation::tilemap_notification(TileMap *p_tile_map, int p_what) { + switch (p_what) { + case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: { + if (p_tile_map->is_inside_tree()) { + Map<Vector2i, TileMapQuadrant> quadrant_map = p_tile_map->get_quadrant_map(); + Transform2D tilemap_xform = p_tile_map->get_global_transform(); + for (Map<Vector2i, TileMapQuadrant>::Element *E_quadrant = quadrant_map.front(); E_quadrant; E_quadrant = E_quadrant->next()) { + TileMapQuadrant &q = E_quadrant->get(); + for (Map<Vector2i, Vector<RID>>::Element *E_region = q.navigation_regions.front(); E_region; E_region = E_region->next()) { + for (int layer_index = 0; layer_index < E_region->get().size(); layer_index++) { + RID region = E_region->get()[layer_index]; + if (!region.is_valid()) { + continue; + } + Transform2D tile_transform; + tile_transform.set_origin(p_tile_map->map_to_world(E_region->key())); + NavigationServer2D::get_singleton()->region_set_transform(region, tilemap_xform * tile_transform); + } + } + } + } + } break; } - return -1; } -void TileSet::reset_state() { - clear(); +void TileSetPluginAtlasNavigation::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + ERR_FAIL_COND(!p_tile_map); + ERR_FAIL_COND(!p_tile_map->is_inside_tree()); + Ref<TileSet> tile_set = p_tile_map->get_tileset(); + ERR_FAIL_COND(!tile_set.is_valid()); + + // Get colors for debug. + SceneTree *st = SceneTree::get_singleton(); + Color debug_navigation_color; + bool debug_navigation = st && st->is_debugging_navigation_hint(); + if (debug_navigation) { + debug_navigation_color = st->get_debug_navigation_color(); + } + + Transform2D tilemap_xform = p_tile_map->get_global_transform(); + SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); + while (q_list_element) { + TileMapQuadrant &q = *q_list_element->self(); + + // Clear navigation shapes in the quadrant. + for (Map<Vector2i, Vector<RID>>::Element *E = q.navigation_regions.front(); E; E = E->next()) { + for (int i = 0; i < E->get().size(); i++) { + RID region = E->get()[i]; + if (!region.is_valid()) { + continue; + } + NavigationServer2D::get_singleton()->region_set_map(region, RID()); + } + } + q.navigation_regions.clear(); + + // Get the navigation polygons and create regions. + for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) { + TileMapCell c = p_tile_map->get_cell(E_cell->get()); + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + q.navigation_regions[E_cell->get()].resize(tile_set->get_navigation_layers_count()); + + for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) { + Ref<NavigationPolygon> navpoly; + navpoly = tile_data->get_navigation_polygon(layer_index); + + if (navpoly.is_valid()) { + Transform2D tile_transform; + tile_transform.set_origin(p_tile_map->map_to_world(E_cell->get())); + + RID region = NavigationServer2D::get_singleton()->region_create(); + NavigationServer2D::get_singleton()->region_set_map(region, p_tile_map->get_world_2d()->get_navigation_map()); + NavigationServer2D::get_singleton()->region_set_transform(region, tilemap_xform * tile_transform); + NavigationServer2D::get_singleton()->region_set_navpoly(region, navpoly); + q.navigation_regions[E_cell->get()].write[layer_index] = region; + } + } + } + } + } + + q_list_element = q_list_element->next(); + } } -void TileSet::clear() { - tile_map.clear(); - notify_property_list_changed(); - emit_changed(); +void TileSetPluginAtlasNavigation::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { + // Clear navigation shapes in the quadrant. + for (Map<Vector2i, Vector<RID>>::Element *E = p_quadrant->navigation_regions.front(); E; E = E->next()) { + for (int i = 0; i < E->get().size(); i++) { + RID region = E->get()[i]; + if (!region.is_valid()) { + continue; + } + NavigationServer2D::get_singleton()->free(region); + } + } + p_quadrant->navigation_regions.clear(); } -void TileSet::_bind_methods() { - ClassDB::bind_method(D_METHOD("create_tile", "id"), &TileSet::create_tile); - ClassDB::bind_method(D_METHOD("autotile_clear_bitmask_map", "id"), &TileSet::autotile_clear_bitmask_map); - ClassDB::bind_method(D_METHOD("autotile_set_icon_coordinate", "id", "coord"), &TileSet::autotile_set_icon_coordinate); - ClassDB::bind_method(D_METHOD("autotile_get_icon_coordinate", "id"), &TileSet::autotile_get_icon_coordinate); - ClassDB::bind_method(D_METHOD("autotile_set_subtile_priority", "id", "coord", "priority"), &TileSet::autotile_set_subtile_priority); - ClassDB::bind_method(D_METHOD("autotile_get_subtile_priority", "id", "coord"), &TileSet::autotile_get_subtile_priority); - ClassDB::bind_method(D_METHOD("autotile_set_z_index", "id", "coord", "z_index"), &TileSet::autotile_set_z_index); - ClassDB::bind_method(D_METHOD("autotile_get_z_index", "id", "coord"), &TileSet::autotile_get_z_index); - ClassDB::bind_method(D_METHOD("autotile_set_light_occluder", "id", "light_occluder", "coord"), &TileSet::autotile_set_light_occluder); - ClassDB::bind_method(D_METHOD("autotile_get_light_occluder", "id", "coord"), &TileSet::autotile_get_light_occluder); - ClassDB::bind_method(D_METHOD("autotile_set_navigation_polygon", "id", "navigation_polygon", "coord"), &TileSet::autotile_set_navigation_polygon); - ClassDB::bind_method(D_METHOD("autotile_get_navigation_polygon", "id", "coord"), &TileSet::autotile_get_navigation_polygon); - ClassDB::bind_method(D_METHOD("autotile_set_bitmask", "id", "bitmask", "flag"), &TileSet::autotile_set_bitmask); - ClassDB::bind_method(D_METHOD("autotile_get_bitmask", "id", "coord"), &TileSet::autotile_get_bitmask); - ClassDB::bind_method(D_METHOD("autotile_set_bitmask_mode", "id", "mode"), &TileSet::autotile_set_bitmask_mode); - ClassDB::bind_method(D_METHOD("autotile_get_bitmask_mode", "id"), &TileSet::autotile_get_bitmask_mode); - ClassDB::bind_method(D_METHOD("autotile_set_spacing", "id", "spacing"), &TileSet::autotile_set_spacing); - ClassDB::bind_method(D_METHOD("autotile_get_spacing", "id"), &TileSet::autotile_get_spacing); - ClassDB::bind_method(D_METHOD("autotile_set_size", "id", "size"), &TileSet::autotile_set_size); - ClassDB::bind_method(D_METHOD("autotile_get_size", "id"), &TileSet::autotile_get_size); - ClassDB::bind_method(D_METHOD("tile_set_name", "id", "name"), &TileSet::tile_set_name); - ClassDB::bind_method(D_METHOD("tile_get_name", "id"), &TileSet::tile_get_name); - ClassDB::bind_method(D_METHOD("tile_set_texture", "id", "texture"), &TileSet::tile_set_texture); - ClassDB::bind_method(D_METHOD("tile_get_texture", "id"), &TileSet::tile_get_texture); - ClassDB::bind_method(D_METHOD("tile_set_material", "id", "material"), &TileSet::tile_set_material); - ClassDB::bind_method(D_METHOD("tile_get_material", "id"), &TileSet::tile_get_material); - ClassDB::bind_method(D_METHOD("tile_set_modulate", "id", "color"), &TileSet::tile_set_modulate); - ClassDB::bind_method(D_METHOD("tile_get_modulate", "id"), &TileSet::tile_get_modulate); - ClassDB::bind_method(D_METHOD("tile_set_texture_offset", "id", "texture_offset"), &TileSet::tile_set_texture_offset); - ClassDB::bind_method(D_METHOD("tile_get_texture_offset", "id"), &TileSet::tile_get_texture_offset); - ClassDB::bind_method(D_METHOD("tile_set_region", "id", "region"), &TileSet::tile_set_region); - ClassDB::bind_method(D_METHOD("tile_get_region", "id"), &TileSet::tile_get_region); - ClassDB::bind_method(D_METHOD("tile_set_shape", "id", "shape_id", "shape"), &TileSet::tile_set_shape); - ClassDB::bind_method(D_METHOD("tile_get_shape", "id", "shape_id"), &TileSet::tile_get_shape); - ClassDB::bind_method(D_METHOD("tile_set_shape_offset", "id", "shape_id", "shape_offset"), &TileSet::tile_set_shape_offset); - ClassDB::bind_method(D_METHOD("tile_get_shape_offset", "id", "shape_id"), &TileSet::tile_get_shape_offset); - ClassDB::bind_method(D_METHOD("tile_set_shape_transform", "id", "shape_id", "shape_transform"), &TileSet::tile_set_shape_transform); - ClassDB::bind_method(D_METHOD("tile_get_shape_transform", "id", "shape_id"), &TileSet::tile_get_shape_transform); - ClassDB::bind_method(D_METHOD("tile_set_shape_one_way", "id", "shape_id", "one_way"), &TileSet::tile_set_shape_one_way); - ClassDB::bind_method(D_METHOD("tile_get_shape_one_way", "id", "shape_id"), &TileSet::tile_get_shape_one_way); - ClassDB::bind_method(D_METHOD("tile_set_shape_one_way_margin", "id", "shape_id", "one_way"), &TileSet::tile_set_shape_one_way_margin); - ClassDB::bind_method(D_METHOD("tile_get_shape_one_way_margin", "id", "shape_id"), &TileSet::tile_get_shape_one_way_margin); - ClassDB::bind_method(D_METHOD("tile_add_shape", "id", "shape", "shape_transform", "one_way", "autotile_coord"), &TileSet::tile_add_shape, DEFVAL(false), DEFVAL(Vector2())); - ClassDB::bind_method(D_METHOD("tile_get_shape_count", "id"), &TileSet::tile_get_shape_count); - ClassDB::bind_method(D_METHOD("tile_set_shapes", "id", "shapes"), &TileSet::_tile_set_shapes); - ClassDB::bind_method(D_METHOD("tile_get_shapes", "id"), &TileSet::_tile_get_shapes); - ClassDB::bind_method(D_METHOD("tile_set_tile_mode", "id", "tilemode"), &TileSet::tile_set_tile_mode); - ClassDB::bind_method(D_METHOD("tile_get_tile_mode", "id"), &TileSet::tile_get_tile_mode); - ClassDB::bind_method(D_METHOD("tile_set_navigation_polygon", "id", "navigation_polygon"), &TileSet::tile_set_navigation_polygon); - ClassDB::bind_method(D_METHOD("tile_get_navigation_polygon", "id"), &TileSet::tile_get_navigation_polygon); - ClassDB::bind_method(D_METHOD("tile_set_navigation_polygon_offset", "id", "navigation_polygon_offset"), &TileSet::tile_set_navigation_polygon_offset); - ClassDB::bind_method(D_METHOD("tile_get_navigation_polygon_offset", "id"), &TileSet::tile_get_navigation_polygon_offset); - ClassDB::bind_method(D_METHOD("tile_set_light_occluder", "id", "light_occluder"), &TileSet::tile_set_light_occluder); - 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); - ClassDB::bind_method(D_METHOD("get_last_unused_tile_id"), &TileSet::get_last_unused_tile_id); - ClassDB::bind_method(D_METHOD("find_tile_by_name", "name"), &TileSet::find_tile_by_name); - ClassDB::bind_method(D_METHOD("get_tiles_ids"), &TileSet::_get_tiles_ids); - - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_tile_bound", PropertyInfo(Variant::INT, "drawn_id"), PropertyInfo(Variant::INT, "neighbor_id"))); - 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_VMETHOD(MethodInfo(Variant::VECTOR2, "_forward_atlas_subtile_selection", PropertyInfo(Variant::INT, "atlastile_id"), 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); - BIND_ENUM_CONSTANT(BIND_TOP); - BIND_ENUM_CONSTANT(BIND_TOPRIGHT); - BIND_ENUM_CONSTANT(BIND_LEFT); - BIND_ENUM_CONSTANT(BIND_CENTER); - BIND_ENUM_CONSTANT(BIND_RIGHT); - BIND_ENUM_CONSTANT(BIND_BOTTOMLEFT); - BIND_ENUM_CONSTANT(BIND_BOTTOM); - BIND_ENUM_CONSTANT(BIND_BOTTOMRIGHT); - - BIND_ENUM_CONSTANT(SINGLE_TILE); - BIND_ENUM_CONSTANT(AUTO_TILE); - BIND_ENUM_CONSTANT(ATLAS_TILE); +void TileSetPluginAtlasNavigation::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { + // Draw the debug collision shapes. + Ref<TileSet> tile_set = p_tile_map->get_tileset(); + ERR_FAIL_COND(!tile_set.is_valid()); + + if (!p_tile_map->get_tree()) { + return; + } + + bool show_navigation = false; + switch (p_tile_map->get_navigation_visibility_mode()) { + case TileMap::VISIBILITY_MODE_DEFAULT: + show_navigation = !Engine::get_singleton()->is_editor_hint() && (p_tile_map->get_tree() && p_tile_map->get_tree()->is_debugging_navigation_hint()); + break; + case TileMap::VISIBILITY_MODE_FORCE_HIDE: + show_navigation = false; + break; + case TileMap::VISIBILITY_MODE_FORCE_SHOW: + show_navigation = true; + break; + } + if (!show_navigation) { + return; + } + + RenderingServer *rs = RenderingServer::get_singleton(); + + Color color = p_tile_map->get_tree()->get_debug_navigation_color(); + RandomPCG rand; + + Vector2 quadrant_pos = p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size()); + + for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { + TileMapCell c = p_tile_map->get_cell(E_cell->get()); + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + + Transform2D xform; + xform.set_origin(p_tile_map->map_to_world(E_cell->get()) - quadrant_pos); + rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform); + + for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) { + Ref<NavigationPolygon> navpoly = tile_data->get_navigation_polygon(layer_index); + if (navpoly.is_valid()) { + PackedVector2Array navigation_polygon_vertices = navpoly->get_vertices(); + + for (int i = 0; i < navpoly->get_polygon_count(); i++) { + // An array of vertices for this polygon. + Vector<int> polygon = navpoly->get_polygon(i); + Vector<Vector2> vertices; + vertices.resize(polygon.size()); + for (int j = 0; j < polygon.size(); j++) { + ERR_FAIL_INDEX(polygon[j], navigation_polygon_vertices.size()); + vertices.write[j] = navigation_polygon_vertices[polygon[j]]; + } + + // Generate the polygon color, slightly randomly modified from the settings one. + Color random_variation_color; + random_variation_color.set_hsv(color.get_h() + rand.random(-1.0, 1.0) * 0.05, color.get_s(), color.get_v() + rand.random(-1.0, 1.0) * 0.1); + random_variation_color.a = color.a; + Vector<Color> colors; + colors.push_back(random_variation_color); + + rs->canvas_item_add_polygon(p_quadrant->debug_canvas_item, vertices, colors); + } + } + } + } + } + } } -TileSet::TileSet() { +/////////////////////////////// TileSetPluginScenesCollections ////////////////////////////////////// + +void TileSetPluginScenesCollections::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + Ref<TileSet> tile_set = p_tile_map->get_tileset(); + ERR_FAIL_COND(!tile_set.is_valid()); + + SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); + while (q_list_element) { + TileMapQuadrant &q = *q_list_element->self(); + + // Clear the scenes. + for (Map<Vector2i, String>::Element *E = q.scenes.front(); E; E = E->next()) { + Node *node = p_tile_map->get_node(E->get()); + if (node) { + node->queue_delete(); + } + } + + q.scenes.clear(); + + // Recreate the scenes. + for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) { + const TileMapCell &c = p_tile_map->get_cell(E_cell->get()); + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } + + TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); + if (scenes_collection_source) { + Ref<PackedScene> packed_scene = scenes_collection_source->get_scene_tile_scene(c.alternative_tile); + if (packed_scene.is_valid()) { + Node *scene = packed_scene->instantiate(); + p_tile_map->add_child(scene); + Control *scene_as_control = Object::cast_to<Control>(scene); + Node2D *scene_as_node2d = Object::cast_to<Node2D>(scene); + if (scene_as_control) { + scene_as_control->set_position(p_tile_map->map_to_world(E_cell->get()) + scene_as_control->get_position()); + } else if (scene_as_node2d) { + Transform2D xform; + xform.set_origin(p_tile_map->map_to_world(E_cell->get())); + scene_as_node2d->set_transform(xform * scene_as_node2d->get_transform()); + } + q.scenes[E_cell->get()] = scene->get_name(); + } + } + } + } + + q_list_element = q_list_element->next(); + } +} + +void TileSetPluginScenesCollections::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { + // Clear the scenes. + for (Map<Vector2i, String>::Element *E = p_quadrant->scenes.front(); E; E = E->next()) { + Node *node = p_tile_map->get_node(E->get()); + if (node) { + node->queue_delete(); + } + } + + p_quadrant->scenes.clear(); +} + +void TileSetPluginScenesCollections::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { + Ref<TileSet> tile_set = p_tile_map->get_tileset(); + ERR_FAIL_COND(!tile_set.is_valid()); + + if (!Engine::get_singleton()->is_editor_hint()) { + return; + } + + // Draw a placeholder for scenes needing one. + RenderingServer *rs = RenderingServer::get_singleton(); + Vector2 quadrant_pos = p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size()); + for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { + const TileMapCell &c = p_tile_map->get_cell(E_cell->get()); + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } + + TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); + if (scenes_collection_source) { + if (!scenes_collection_source->get_scene_tile_scene(c.alternative_tile).is_valid() || scenes_collection_source->get_scene_tile_display_placeholder(c.alternative_tile)) { + // Generate a random color from the hashed values of the tiles. + Array to_hash; + to_hash.push_back(c.source_id); + to_hash.push_back(c.alternative_tile); + uint32_t hash = RandomPCG(to_hash.hash()).rand(); + + Color color; + color = color.from_hsv( + (float)((hash >> 24) & 0xFF) / 256.0, + Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0), + Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0), + 0.8); + + // Draw a placeholder tile. + Transform2D xform; + xform.set_origin(p_tile_map->map_to_world(E_cell->get()) - quadrant_pos); + rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform); + rs->canvas_item_add_circle(p_quadrant->debug_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color); + } + } + } + } } diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index 0a8721f35b..dbf6dbabe6 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -32,226 +32,676 @@ #define TILE_SET_H #include "core/io/resource.h" -#include "core/variant/array.h" +#include "core/object/object.h" +#include "core/templates/local_vector.h" #include "scene/2d/light_occluder_2d.h" #include "scene/2d/navigation_region_2d.h" +#include "scene/main/canvas_item.h" +#include "scene/resources/concave_polygon_shape_2d.h" #include "scene/resources/convex_polygon_shape_2d.h" +#include "scene/resources/packed_scene.h" +#include "scene/resources/physics_material.h" #include "scene/resources/shape_2d.h" + +#ifndef DISABLE_DEPRECATED +#include "scene/resources/shader.h" #include "scene/resources/texture.h" +#endif + +class TileMap; +struct TileMapQuadrant; +class TileSetSource; +class TileSetAtlasSource; +class TileData; + +// Forward-declare the plugins. +class TileSetPlugin; +class TileSetPluginAtlasRendering; +class TileSetPluginAtlasPhysics; +class TileSetPluginAtlasNavigation; class TileSet : public Resource { GDCLASS(TileSet, Resource); -public: - struct ShapeData { +#ifndef DISABLE_DEPRECATED +private: + struct CompatibilityShapeData { + Vector2i autotile_coords; + bool one_way; + float one_way_margin; Ref<Shape2D> shape; - Transform2D shape_transform; - Vector2 autotile_coord; - bool one_way_collision = false; - float one_way_collision_margin = 1.0; + Transform2D transform; + }; - ShapeData() {} + struct CompatibilityTileData { + String name; + Ref<Texture2D> texture; + Vector2 tex_offset; + Ref<ShaderMaterial> material; + Rect2 region; + int tile_mode; + Color modulate; + + // Atlas or autotiles data + int autotile_bitmask_mode; + Vector2 autotile_icon_coordinate; + Size2i autotile_tile_size = Size2i(16, 16); + + int autotile_spacing; + Map<Vector2i, int> autotile_bitmask_flags; + Map<Vector2i, Ref<OccluderPolygon2D>> autotile_occluder_map; + Map<Vector2i, Ref<NavigationPolygon>> autotile_navpoly_map; + Map<Vector2i, int> autotile_priority_map; + Map<Vector2i, int> autotile_z_index_map; + + Vector<CompatibilityShapeData> shapes; + Ref<OccluderPolygon2D> occluder; + Vector2 occluder_offset; + Ref<NavigationPolygon> navigation; + Vector2 navigation_offset; + int z_index; }; - enum BitmaskMode { - BITMASK_2X2, - BITMASK_3X3_MINIMAL, - BITMASK_3X3 + Map<int, CompatibilityTileData *> compatibility_data = Map<int, CompatibilityTileData *>(); + Map<int, int> compatibility_source_mapping = Map<int, int>(); + +private: + void compatibility_conversion(); + +public: + int compatibility_get_source_for_tile_id(int p_old_source) { + return compatibility_source_mapping[p_old_source]; }; - enum AutotileBindings { - BIND_TOPLEFT = 1, - BIND_TOP = 2, - BIND_TOPRIGHT = 4, - BIND_LEFT = 8, - BIND_CENTER = 16, - BIND_RIGHT = 32, - BIND_BOTTOMLEFT = 64, - BIND_BOTTOM = 128, - BIND_BOTTOMRIGHT = 256, - - BIND_IGNORE_TOPLEFT = 1 << 16, - BIND_IGNORE_TOP = 1 << 17, - BIND_IGNORE_TOPRIGHT = 1 << 18, - BIND_IGNORE_LEFT = 1 << 19, - BIND_IGNORE_CENTER = 1 << 20, - BIND_IGNORE_RIGHT = 1 << 21, - BIND_IGNORE_BOTTOMLEFT = 1 << 22, - BIND_IGNORE_BOTTOM = 1 << 23, - BIND_IGNORE_BOTTOMRIGHT = 1 << 24 +#endif // DISABLE_DEPRECATED + +public: + enum CellNeighbor { + CELL_NEIGHBOR_RIGHT_SIDE = 0, + CELL_NEIGHBOR_RIGHT_CORNER, + CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, + CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER, + CELL_NEIGHBOR_BOTTOM_SIDE, + CELL_NEIGHBOR_BOTTOM_CORNER, + CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, + CELL_NEIGHBOR_BOTTOM_LEFT_CORNER, + CELL_NEIGHBOR_LEFT_SIDE, + CELL_NEIGHBOR_LEFT_CORNER, + CELL_NEIGHBOR_TOP_LEFT_SIDE, + CELL_NEIGHBOR_TOP_LEFT_CORNER, + CELL_NEIGHBOR_TOP_SIDE, + CELL_NEIGHBOR_TOP_CORNER, + CELL_NEIGHBOR_TOP_RIGHT_SIDE, + CELL_NEIGHBOR_TOP_RIGHT_CORNER, + CELL_NEIGHBOR_MAX, }; - enum TileMode { - SINGLE_TILE, - AUTO_TILE, - ATLAS_TILE + static const char *CELL_NEIGHBOR_ENUM_TO_TEXT[]; + + enum TerrainMode { + TERRAIN_MODE_MATCH_CORNERS_AND_SIDES = 0, + TERRAIN_MODE_MATCH_CORNERS, + TERRAIN_MODE_MATCH_SIDES, }; - struct AutotileData { - BitmaskMode bitmask_mode = BITMASK_2X2; - // Default size to prevent invalid value - Size2 size = Size2(64, 64); - Vector2 icon_coord = Vector2(0, 0); - int spacing = 0; - Map<Vector2, uint32_t> flags; - Map<Vector2, Ref<OccluderPolygon2D>> occluder_map; - Map<Vector2, Ref<NavigationPolygon>> navpoly_map; - Map<Vector2, int> priority_map; - Map<Vector2, int> z_index_map; - - explicit AutotileData() {} + enum TileShape { + TILE_SHAPE_SQUARE, + TILE_SHAPE_ISOMETRIC, + TILE_SHAPE_HALF_OFFSET_SQUARE, + TILE_SHAPE_HEXAGON, }; -private: - struct TileData { - String name; - Ref<Texture2D> texture; - Vector2 offset; - Rect2i region; - Vector<ShapeData> shapes_data; - Vector2 occluder_offset; - Ref<OccluderPolygon2D> occluder; - Vector2 navigation_polygon_offset; - Ref<NavigationPolygon> navigation_polygon; - Ref<ShaderMaterial> material; - TileMode tile_mode = SINGLE_TILE; - // Default modulate for back-compat - Color modulate = Color(1, 1, 1); - AutotileData autotile_data; - int z_index = 0; + enum TileLayout { + TILE_LAYOUT_STACKED, + TILE_LAYOUT_STACKED_OFFSET, + TILE_LAYOUT_STAIRS_RIGHT, + TILE_LAYOUT_STAIRS_DOWN, + TILE_LAYOUT_DIAMOND_RIGHT, + TILE_LAYOUT_DIAMOND_DOWN, + }; - explicit TileData() {} + enum TileOffsetAxis { + TILE_OFFSET_AXIS_HORIZONTAL, + TILE_OFFSET_AXIS_VERTICAL, }; - Map<int, TileData> tile_map; +public: + struct PackedSceneSource { + Ref<PackedScene> scene; + Vector2 offset; + }; protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; - void _tile_set_shapes(int p_id, const Array &p_shapes); - Array _tile_get_shapes(int p_id) const; - Array _get_tiles_ids() const; - void _decompose_convex_shape(Ref<Shape2D> p_shape); +private: + // --- TileSet data --- + // Basic shape and layout. + TileShape tile_shape = TILE_SHAPE_SQUARE; + TileLayout tile_layout = TILE_LAYOUT_STACKED; + TileOffsetAxis tile_offset_axis = TILE_OFFSET_AXIS_HORIZONTAL; + Size2i tile_size = Size2i(16, 16); //Size2(64, 64); + Vector2 tile_skew = Vector2(0, 0); + + // Rendering. + bool y_sorting = false; + bool uv_clipping = false; + struct OcclusionLayer { + uint32_t light_mask = 1; + bool sdf_collision = false; + }; + Vector<OcclusionLayer> occlusion_layers; + + Ref<ArrayMesh> tile_lines_mesh; + Ref<ArrayMesh> tile_filled_mesh; + bool tile_meshes_dirty = true; + + // Physics + struct PhysicsLayer { + uint32_t collision_layer = 1; + uint32_t collision_mask = 1; + Ref<PhysicsMaterial> physics_material; + }; + Vector<PhysicsLayer> physics_layers; + + // Terrains + struct Terrain { + String name; + Color color; + }; + struct TerrainSet { + TerrainMode mode = TERRAIN_MODE_MATCH_CORNERS_AND_SIDES; + Vector<Terrain> terrains; + }; + Vector<TerrainSet> terrain_sets; + + Map<TerrainMode, Map<CellNeighbor, Ref<ArrayMesh>>> terrain_bits_meshes; + bool terrain_bits_meshes_dirty = true; + + // Navigation + struct Navigationlayer { + uint32_t layers = 1; + }; + Vector<Navigationlayer> navigation_layers; + + // CustomData + struct CustomDataLayer { + String name; + Variant::Type type = Variant::NIL; + }; + Vector<CustomDataLayer> custom_data_layers; + Map<String, int> custom_data_layers_by_name; + + // Per Atlas source data. + Map<int, Ref<TileSetSource>> sources; + Vector<int> source_ids; + int next_source_id = 0; + // --------------------- + + // Plugins themselves. + Vector<TileSetPlugin *> tile_set_plugins_vector; + + void _compute_next_source_id(); + void _source_changed(); + + // Helpers + Vector<Point2> _get_square_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + Vector<Point2> _get_square_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + Vector<Point2> _get_square_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + + Vector<Point2> _get_isometric_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + Vector<Point2> _get_isometric_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + Vector<Point2> _get_isometric_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + + Vector<Point2> _get_half_offset_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis); + Vector<Point2> _get_half_offset_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis); + Vector<Point2> _get_half_offset_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis); + +protected: static void _bind_methods(); +public: + // --- Plugins --- + Vector<TileSetPlugin *> get_tile_set_atlas_plugins() const; + + // --- Accessors for TileSet data --- + + // -- Shape and layout -- + void set_tile_shape(TileShape p_shape); + TileShape get_tile_shape() const; + void set_tile_layout(TileLayout p_layout); + TileLayout get_tile_layout() const; + void set_tile_offset_axis(TileOffsetAxis p_alignment); + TileOffsetAxis get_tile_offset_axis() const; + void set_tile_size(Size2i p_size); + Size2i get_tile_size() const; + + // -- Sources management -- + int get_next_source_id() const; + int get_source_count() const; + int get_source_id(int p_index) const; + int add_source(Ref<TileSetSource> p_tile_set_source, int p_source_id_override = -1); + void set_source_id(int p_source_id, int p_new_id); + void remove_source(int p_source_id); + bool has_source(int p_source_id) const; + Ref<TileSetSource> get_source(int p_source_id) const; + + // Rendering + void set_y_sorting(bool p_y_sort); + bool is_y_sorting() const; + + void set_uv_clipping(bool p_uv_clipping); + bool is_uv_clipping() const; + + void set_occlusion_layers_count(int p_occlusion_layers_count); + int get_occlusion_layers_count() const; + void set_occlusion_layer_light_mask(int p_layer_index, int p_light_mask); + int get_occlusion_layer_light_mask(int p_layer_index) const; + void set_occlusion_layer_sdf_collision(int p_layer_index, int p_sdf_collision); + bool get_occlusion_layer_sdf_collision(int p_layer_index) const; + + // Physics + void set_physics_layers_count(int p_physics_layers_count); + int get_physics_layers_count() const; + void set_physics_layer_collision_layer(int p_layer_index, uint32_t p_layer); + uint32_t get_physics_layer_collision_layer(int p_layer_index) const; + void set_physics_layer_collision_mask(int p_layer_index, uint32_t p_mask); + uint32_t get_physics_layer_collision_mask(int p_layer_index) const; + void set_physics_layer_physics_material(int p_layer_index, Ref<PhysicsMaterial> p_physics_material); + Ref<PhysicsMaterial> get_physics_layer_physics_material(int p_layer_index) const; + + // Terrains + void set_terrain_sets_count(int p_terrains_sets_count); + int get_terrain_sets_count() const; + void set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode); + TerrainMode get_terrain_set_mode(int p_terrain_set) const; + void set_terrains_count(int p_terrain_set, int p_terrains_count); + int get_terrains_count(int p_terrain_set) const; + void set_terrain_name(int p_terrain_set, int p_terrain_index, String p_name); + String get_terrain_name(int p_terrain_set, int p_terrain_index) const; + void set_terrain_color(int p_terrain_set, int p_terrain_index, Color p_color); + Color get_terrain_color(int p_terrain_set, int p_terrain_index) const; + bool is_valid_peering_bit_for_mode(TileSet::TerrainMode p_terrain_mode, TileSet::CellNeighbor p_peering_bit) const; + bool is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const; + + // Navigation + void set_navigation_layers_count(int p_navigation_layers_count); + int get_navigation_layers_count() const; + void set_navigation_layer_layers(int p_layer_index, uint32_t p_layers); + uint32_t get_navigation_layer_layers(int p_layer_index) const; + + // Custom data + void set_custom_data_layers_count(int p_custom_data_layers_count); + int get_custom_data_layers_count() const; + int get_custom_data_layer_by_name(String p_value) const; + void set_custom_data_name(int p_layer_id, String p_value); + String get_custom_data_name(int p_layer_id) const; + void set_custom_data_type(int p_layer_id, Variant::Type p_value); + Variant::Type get_custom_data_type(int p_layer_id) const; + + // Helpers + Vector<Vector2> get_tile_shape_polygon(); + void draw_tile_shape(CanvasItem *p_canvas_item, Rect2 p_region, Color p_color, bool p_filled = false, Ref<Texture2D> p_texture = Ref<Texture2D>()); + + Vector<Point2> get_terrain_bit_polygon(int p_terrain_set, TileSet::CellNeighbor p_bit); + void draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, const TileData *p_tile_data); + Vector<Vector<Ref<Texture2D>>> generate_terrains_icons(Size2i p_size); + + // Resource management virtual void reset_state() override; + TileSet(); + ~TileSet(); +}; + +class TileSetSource : public Resource { + GDCLASS(TileSetSource, Resource); + +protected: + const TileSet *tile_set = nullptr; + public: - void create_tile(int p_id); + static const Vector2i INVALID_ATLAS_COORDS; // Vector2i(-1, -1); + static const int INVALID_TILE_ALTERNATIVE; // -1; + + // Not exposed. + virtual void set_tile_set(const TileSet *p_tile_set); + virtual void notify_tile_data_properties_should_change(){}; + virtual void reset_state() override{}; + + // Tiles. + virtual int get_tiles_count() const = 0; + virtual Vector2i get_tile_id(int tile_index) const = 0; + virtual bool has_tile(Vector2i p_atlas_coords) const = 0; + + // Alternative tiles. + virtual int get_alternative_tiles_count(const Vector2i p_atlas_coords) const = 0; + virtual int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const = 0; + virtual bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const = 0; +}; - void autotile_set_bitmask_mode(int p_id, BitmaskMode p_mode); - BitmaskMode autotile_get_bitmask_mode(int p_id) const; +class TileSetAtlasSource : public TileSetSource { + GDCLASS(TileSetAtlasSource, TileSetSource); - void tile_set_name(int p_id, const String &p_name); - String tile_get_name(int p_id) const; +public: + struct TileAlternativesData { + Vector2i size_in_atlas = Vector2i(1, 1); + Vector2i texture_offset; + Map<int, TileData *> alternatives; + Vector<int> alternatives_ids; + int next_alternative_id = 1; + }; - void tile_set_texture(int p_id, const Ref<Texture2D> &p_texture); - Ref<Texture2D> tile_get_texture(int p_id) const; +private: + Ref<Texture2D> texture; + Vector2i margins; + Vector2i separation; + Size2i texture_region_size = Size2i(16, 16); - void tile_set_texture_offset(int p_id, const Vector2 &p_offset); - Vector2 tile_get_texture_offset(int p_id) const; + Map<Vector2i, TileAlternativesData> tiles; + Vector<Vector2i> tiles_ids; + Map<Vector2i, Vector2i> _coords_mapping_cache; // Maps any coordinate to the including tile - void tile_set_region(int p_id, const Rect2 &p_region); - Rect2 tile_get_region(int p_id) const; + TileData *_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile); + const TileData *_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile) const; - void tile_set_tile_mode(int p_id, TileMode p_tile_mode); - TileMode tile_get_tile_mode(int p_id) const; + void _compute_next_alternative_id(const Vector2i p_atlas_coords); - void autotile_set_icon_coordinate(int p_id, Vector2 coord); - Vector2 autotile_get_icon_coordinate(int p_id) const; +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; - void autotile_set_spacing(int p_id, int p_spacing); - int autotile_get_spacing(int p_id) const; + static void _bind_methods(); - void autotile_set_size(int p_id, Size2 p_size); - Size2 autotile_get_size(int p_id) const; +public: + // Not exposed. + virtual void set_tile_set(const TileSet *p_tile_set) override; + virtual void notify_tile_data_properties_should_change() override; + virtual void reset_state() override; - void autotile_clear_bitmask_map(int p_id); - void autotile_set_subtile_priority(int p_id, const Vector2 &p_coord, int p_priority); - int autotile_get_subtile_priority(int p_id, const Vector2 &p_coord); - const Map<Vector2, int> &autotile_get_priority_map(int p_id) const; + // Base properties. + void set_texture(Ref<Texture2D> p_texture); + Ref<Texture2D> get_texture() const; + void set_margins(Vector2i p_margins); + Vector2i get_margins() const; + void set_separation(Vector2i p_separation); + Vector2i get_separation() const; + void set_texture_region_size(Vector2i p_tile_size); + Vector2i get_texture_region_size() const; + + // Base tiles. + void create_tile(const Vector2i p_atlas_coords, const Vector2i p_size = Vector2i(1, 1)); // Create a tile if it does not exists, or add alternative tile if it does. + void remove_tile(Vector2i p_atlas_coords); // Remove a tile. If p_tile_key.alternative_tile if different from 0, remove the alternative + virtual bool has_tile(Vector2i p_atlas_coords) const override; + bool can_move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords = INVALID_ATLAS_COORDS, Vector2i p_new_size = Vector2i(-1, -1)) const; + void move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords = INVALID_ATLAS_COORDS, Vector2i p_new_size = Vector2i(-1, -1)); + Vector2i get_tile_size_in_atlas(Vector2i p_atlas_coords) const; + + virtual int get_tiles_count() const override; + virtual Vector2i get_tile_id(int p_index) const override; + + Vector2i get_tile_at_coords(Vector2i p_atlas_coords) const; + + // Alternative tiles. + int create_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_id_override = -1); + void remove_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile); + void set_alternative_tile_id(const Vector2i p_atlas_coords, int p_alternative_tile, int p_new_id); + virtual bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const override; + int get_next_alternative_tile_id(const Vector2i p_atlas_coords) const; + + virtual int get_alternative_tiles_count(const Vector2i p_atlas_coords) const override; + virtual int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const override; + + // Get data associated to a tile. + Object *get_tile_data(const Vector2i p_atlas_coords, int p_alternative_tile) const; + + // Helpers. + Vector2i get_atlas_grid_size() const; + bool has_tiles_outside_texture(); + void clear_tiles_outside_texture(); + Rect2i get_tile_texture_region(Vector2i p_atlas_coords) const; + Vector2i get_tile_effective_texture_offset(Vector2i p_atlas_coords, int p_alternative_tile) const; + + ~TileSetAtlasSource(); +}; - void autotile_set_z_index(int p_id, const Vector2 &p_coord, int p_z_index); - int autotile_get_z_index(int p_id, const Vector2 &p_coord); - const Map<Vector2, int> &autotile_get_z_index_map(int p_id) const; +class TileSetScenesCollectionSource : public TileSetSource { + GDCLASS(TileSetScenesCollectionSource, TileSetSource); - void autotile_set_bitmask(int p_id, Vector2 p_coord, uint32_t p_flag); - uint32_t autotile_get_bitmask(int p_id, Vector2 p_coord); - const Map<Vector2, uint32_t> &autotile_get_bitmask_map(int p_id); - Vector2 autotile_get_subtile_for_bitmask(int p_id, uint16_t p_bitmask, const Node *p_tilemap_node = nullptr, const Vector2 &p_tile_location = Vector2()); - Vector2 atlastile_get_subtile_by_priority(int p_id, const Node *p_tilemap_node = nullptr, const Vector2 &p_tile_location = Vector2()); +private: + struct SceneData { + Ref<PackedScene> scene; + bool display_placeholder = false; + }; + Vector<int> scenes_ids; + Map<int, SceneData> scenes; + int next_scene_id = 1; - void tile_set_shape(int p_id, int p_shape_id, const Ref<Shape2D> &p_shape); - Ref<Shape2D> tile_get_shape(int p_id, int p_shape_id) const; + void _compute_next_alternative_id(); - void tile_set_shape_transform(int p_id, int p_shape_id, const Transform2D &p_offset); - Transform2D tile_get_shape_transform(int p_id, int p_shape_id) const; +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; - void tile_set_shape_offset(int p_id, int p_shape_id, const Vector2 &p_offset); - Vector2 tile_get_shape_offset(int p_id, int p_shape_id) const; + static void _bind_methods(); - void tile_set_shape_one_way(int p_id, int p_shape_id, bool p_one_way); - bool tile_get_shape_one_way(int p_id, int p_shape_id) const; +public: + // Tiles. + int get_tiles_count() const override; + Vector2i get_tile_id(int p_tile_index) const override; + bool has_tile(Vector2i p_atlas_coords) const override; + + // Alternative tiles. + int get_alternative_tiles_count(const Vector2i p_atlas_coords) const override; + int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const override; + bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const override; + + // Scenes sccessors. Lot are similar to "Alternative tiles". + int get_scene_tiles_count() { return get_alternative_tiles_count(Vector2i()); } + int get_scene_tile_id(int p_index) { return get_alternative_tile_id(Vector2i(), p_index); }; + bool has_scene_tile_id(int p_id) { return has_alternative_tile(Vector2i(), p_id); }; + int create_scene_tile(Ref<PackedScene> p_packed_scene = Ref<PackedScene>(), int p_id_override = -1); + void set_scene_tile_id(int p_id, int p_new_id); + void set_scene_tile_scene(int p_id, Ref<PackedScene> p_packed_scene); + Ref<PackedScene> get_scene_tile_scene(int p_id) const; + void set_scene_tile_display_placeholder(int p_id, bool p_packed_scene); + bool get_scene_tile_display_placeholder(int p_id) const; + void remove_scene_tile(int p_id); + int get_next_scene_tile_id() const; +}; - void tile_set_shape_one_way_margin(int p_id, int p_shape_id, float p_margin); - float tile_get_shape_one_way_margin(int p_id, int p_shape_id) const; +class TileData : public Object { + GDCLASS(TileData, Object); - void tile_clear_shapes(int p_id); - void tile_add_shape(int p_id, const Ref<Shape2D> &p_shape, const Transform2D &p_transform, bool p_one_way = false, const Vector2 &p_autotile_coord = Vector2()); - int tile_get_shape_count(int p_id) const; +private: + const TileSet *tile_set = nullptr; + bool allow_transform = true; + + // Rendering + bool flip_h = false; + bool flip_v = false; + bool transpose = false; + Vector2i tex_offset = Vector2i(); + Ref<ShaderMaterial> material = Ref<ShaderMaterial>(); + Color modulate = Color(1.0, 1.0, 1.0, 1.0); + int z_index = 0; + int y_sort_origin = 0; + Vector<Ref<OccluderPolygon2D>> occluders; + + // Physics + struct PhysicsLayerTileData { + struct PolygonShapeTileData { + LocalVector<Vector2> polygon; + LocalVector<Ref<ConvexPolygonShape2D>> shapes; + bool one_way = false; + float one_way_margin = 1.0; + }; + + Vector<PolygonShapeTileData> polygons; + }; + Vector<PhysicsLayerTileData> physics; + // TODO add support for areas. - void tile_set_shapes(int p_id, const Vector<ShapeData> &p_shapes); - Vector<ShapeData> tile_get_shapes(int p_id) const; + // Terrain + int terrain_set = -1; + int terrain_peering_bits[16] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; - void tile_set_material(int p_id, const Ref<ShaderMaterial> &p_material); - Ref<ShaderMaterial> tile_get_material(int p_id) const; + // Navigation + Vector<Ref<NavigationPolygon>> navigation; - void tile_set_modulate(int p_id, const Color &p_modulate); - Color tile_get_modulate(int p_id) const; + // Misc + double probability = 1.0; - void tile_set_occluder_offset(int p_id, const Vector2 &p_offset); - Vector2 tile_get_occluder_offset(int p_id) const; + // Custom data + Vector<Variant> custom_data; - void tile_set_light_occluder(int p_id, const Ref<OccluderPolygon2D> &p_light_occluder); - Ref<OccluderPolygon2D> tile_get_light_occluder(int p_id) const; +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + static void _bind_methods(); - void autotile_set_light_occluder(int p_id, const Ref<OccluderPolygon2D> &p_light_occluder, const Vector2 &p_coord); - Ref<OccluderPolygon2D> autotile_get_light_occluder(int p_id, const Vector2 &p_coord) const; - const Map<Vector2, Ref<OccluderPolygon2D>> &autotile_get_light_oclusion_map(int p_id) const; +public: + // Not exposed. + void set_tile_set(const TileSet *p_tile_set); + void notify_tile_data_properties_should_change(); + void reset_state(); + void set_allow_transform(bool p_allow_transform); + bool is_allowing_transform() const; + + // Rendering + void set_flip_h(bool p_flip_h); + bool get_flip_h() const; + void set_flip_v(bool p_flip_v); + bool get_flip_v() const; + void set_transpose(bool p_transpose); + bool get_transpose() const; + + void set_texture_offset(Vector2i p_texture_offset); + Vector2i get_texture_offset() const; + void tile_set_material(Ref<ShaderMaterial> p_material); + Ref<ShaderMaterial> tile_get_material() const; + void set_modulate(Color p_modulate); + Color get_modulate() const; + void set_z_index(int p_z_index); + int get_z_index() const; + void set_y_sort_origin(int p_y_sort_origin); + int get_y_sort_origin() const; + + void set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_polygon); + Ref<OccluderPolygon2D> get_occluder(int p_layer_id) const; + + // Physics + int get_collision_polygons_count(int p_layer_id) const; + void set_collision_polygons_count(int p_layer_id, int p_shapes_count); + void add_collision_polygon(int p_layer_id); + void remove_collision_polygon(int p_layer_id, int p_polygon_index); + void set_collision_polygon_points(int p_layer_id, int p_polygon_index, Vector<Vector2> p_polygon); + Vector<Vector2> get_collision_polygon_points(int p_layer_id, int p_polygon_index) const; + void set_collision_polygon_one_way(int p_layer_id, int p_polygon_index, bool p_one_way); + bool is_collision_polygon_one_way(int p_layer_id, int p_polygon_index) const; + void set_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index, float p_one_way_margin); + float get_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index) const; + int get_collision_polygon_shapes_count(int p_layer_id, int p_polygon_index) const; + Ref<ConvexPolygonShape2D> get_collision_polygon_shape(int p_layer_id, int p_polygon_index, int shape_index) const; + + // Terrain + void set_terrain_set(int p_terrain_id); + int get_terrain_set() const; + void set_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit, int p_terrain_id); + int get_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) const; + bool is_valid_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) const; + + // Navigation + void set_navigation_polygon(int p_layer_id, Ref<NavigationPolygon> p_navigation_polygon); + Ref<NavigationPolygon> get_navigation_polygon(int p_layer_id) const; + + // Misc + void set_probability(float p_probability); + float get_probability() const; + + // Custom data. + void set_custom_data(String p_layer_name, Variant p_value); + Variant get_custom_data(String p_layer_name) const; + void set_custom_data_by_layer_id(int p_layer_id, Variant p_value); + Variant get_custom_data_by_layer_id(int p_layer_id) const; +}; - void tile_set_navigation_polygon_offset(int p_id, const Vector2 &p_offset); - Vector2 tile_get_navigation_polygon_offset(int p_id) const; +#include "scene/2d/tile_map.h" - void tile_set_navigation_polygon(int p_id, const Ref<NavigationPolygon> &p_navigation_polygon); - Ref<NavigationPolygon> tile_get_navigation_polygon(int p_id) const; +class TileSetPlugin : public Object { + GDCLASS(TileSetPlugin, Object); - void autotile_set_navigation_polygon(int p_id, const Ref<NavigationPolygon> &p_navigation_polygon, const Vector2 &p_coord); - 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; +public: + // Tilemap updates. + virtual void tilemap_notification(TileMap *p_tile_map, int p_what){}; + virtual void update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list){}; + virtual void create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant){}; + virtual void cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant){}; - void tile_set_z_index(int p_id, int p_z_index); - int tile_get_z_index(int p_id) const; + virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant){}; +}; + +class TileSetPluginAtlasRendering : public TileSetPlugin { + GDCLASS(TileSetPluginAtlasRendering, TileSetPlugin); + +private: + static constexpr float fp_adjust = 0.00001; + bool quadrant_order_dirty = false; - void remove_tile(int p_id); +public: + // Tilemap updates + virtual void tilemap_notification(TileMap *p_tile_map, int p_what) override; + virtual void update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) override; + virtual void create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; + virtual void cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; + virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; + + // Other. + static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0)); +}; - bool has_tile(int p_id) const; +class TileSetPluginAtlasPhysics : public TileSetPlugin { + GDCLASS(TileSetPluginAtlasPhysics, TileSetPlugin); - bool is_tile_bound(int p_drawn_id, int p_neighbor_id); +public: + // Tilemap updates + virtual void tilemap_notification(TileMap *p_tile_map, int p_what) override; + virtual void update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) override; + virtual void create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; + virtual void cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; + virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; +}; - int find_tile_by_name(const String &p_name) const; - void get_tile_list(List<int> *p_tiles) const; +class TileSetPluginAtlasNavigation : public TileSetPlugin { + GDCLASS(TileSetPluginAtlasNavigation, TileSetPlugin); - void clear(); +public: + // Tilemap updates + virtual void tilemap_notification(TileMap *p_tile_map, int p_what) override; + virtual void update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) override; + virtual void cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; + virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; +}; - int get_last_unused_tile_id() const; +class TileSetPluginScenesCollections : public TileSetPlugin { + GDCLASS(TileSetPluginScenesCollections, TileSetPlugin); - TileSet(); +public: + // Tilemap updates + virtual void update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) override; + virtual void cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; + virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; }; -VARIANT_ENUM_CAST(TileSet::AutotileBindings); -VARIANT_ENUM_CAST(TileSet::BitmaskMode); -VARIANT_ENUM_CAST(TileSet::TileMode); +VARIANT_ENUM_CAST(TileSet::CellNeighbor); +VARIANT_ENUM_CAST(TileSet::TerrainMode); +VARIANT_ENUM_CAST(TileSet::TileShape); +VARIANT_ENUM_CAST(TileSet::TileLayout); +VARIANT_ENUM_CAST(TileSet::TileOffsetAxis); #endif // TILE_SET_H diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 859546694f..36a1c206c8 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -33,6 +33,7 @@ #include "core/templates/vmap.h" #include "servers/rendering/shader_types.h" #include "visual_shader_nodes.h" +#include "visual_shader_particle_nodes.h" #include "visual_shader_sdf_nodes.h" bool VisualShaderNode::is_simple_decl() const { @@ -60,6 +61,20 @@ Variant VisualShaderNode::get_input_port_default_value(int p_port) const { return Variant(); } +void VisualShaderNode::remove_input_port_default_value(int p_port) { + if (default_input_values.has(p_port)) { + default_input_values.erase(p_port); + emit_changed(); + } +} + +void VisualShaderNode::clear_default_input_values() { + if (!default_input_values.is_empty()) { + default_input_values.clear(); + emit_changed(); + } +} + bool VisualShaderNode::is_port_separator(int p_index) const { return false; } @@ -94,6 +109,52 @@ bool VisualShaderNode::is_generate_input_var(int p_port) const { return true; } +bool VisualShaderNode::is_output_port_expandable(int p_port) const { + return false; +} + +void VisualShaderNode::_set_output_ports_expanded(const Array &p_values) { + for (int i = 0; i < p_values.size(); i++) { + expanded_output_ports[p_values[i]] = true; + } + emit_changed(); +} + +Array VisualShaderNode::_get_output_ports_expanded() const { + Array arr; + for (int i = 0; i < get_output_port_count(); i++) { + if (_is_output_port_expanded(i)) { + arr.push_back(i); + } + } + return arr; +} + +void VisualShaderNode::_set_output_port_expanded(int p_port, bool p_expanded) { + expanded_output_ports[p_port] = p_expanded; + emit_changed(); +} + +bool VisualShaderNode::_is_output_port_expanded(int p_port) const { + if (expanded_output_ports.has(p_port)) { + return expanded_output_ports[p_port]; + } + return false; +} + +int VisualShaderNode::get_expanded_output_port_count() const { + int count = get_output_port_count(); + int count2 = count; + for (int i = 0; i < count; i++) { + if (is_output_port_expandable(i) && _is_output_port_expanded(i)) { + if (get_output_port_type(i) == PORT_TYPE_VECTOR) { + count2 += 3; + } + } + } + return count2; +} + bool VisualShaderNode::is_code_generated() const { return true; } @@ -106,6 +167,14 @@ bool VisualShaderNode::is_use_prop_slots() const { return false; } +bool VisualShaderNode::is_disabled() const { + return disabled; +} + +void VisualShaderNode::set_disabled(bool p_disabled) { + disabled = p_disabled; +} + Vector<VisualShader::DefaultTextureParam> VisualShaderNode::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { return Vector<VisualShader::DefaultTextureParam>(); } @@ -157,14 +226,24 @@ void VisualShaderNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_output_port_for_preview", "port"), &VisualShaderNode::set_output_port_for_preview); ClassDB::bind_method(D_METHOD("get_output_port_for_preview"), &VisualShaderNode::get_output_port_for_preview); + ClassDB::bind_method(D_METHOD("_set_output_port_expanded", "port"), &VisualShaderNode::_set_output_port_expanded); + ClassDB::bind_method(D_METHOD("_is_output_port_expanded"), &VisualShaderNode::_is_output_port_expanded); + + ClassDB::bind_method(D_METHOD("_set_output_ports_expanded", "values"), &VisualShaderNode::_set_output_ports_expanded); + ClassDB::bind_method(D_METHOD("_get_output_ports_expanded"), &VisualShaderNode::_get_output_ports_expanded); + ClassDB::bind_method(D_METHOD("set_input_port_default_value", "port", "value"), &VisualShaderNode::set_input_port_default_value); ClassDB::bind_method(D_METHOD("get_input_port_default_value", "port"), &VisualShaderNode::get_input_port_default_value); + ClassDB::bind_method(D_METHOD("remove_input_port_default_value", "port"), &VisualShaderNode::remove_input_port_default_value); + ClassDB::bind_method(D_METHOD("clear_default_input_values"), &VisualShaderNode::clear_default_input_values); + ClassDB::bind_method(D_METHOD("set_default_input_values", "values"), &VisualShaderNode::set_default_input_values); ClassDB::bind_method(D_METHOD("get_default_input_values"), &VisualShaderNode::get_default_input_values); ADD_PROPERTY(PropertyInfo(Variant::INT, "output_port_for_preview"), "set_output_port_for_preview", "get_output_port_for_preview"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "default_input_values", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_default_input_values", "get_default_input_values"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "expanded_output_ports", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_output_ports_expanded", "_get_output_ports_expanded"); ADD_SIGNAL(MethodInfo("editor_refresh_request")); BIND_ENUM_CONSTANT(PORT_TYPE_SCALAR); @@ -300,6 +379,42 @@ String VisualShaderNodeCustom::generate_global_per_node(Shader::Mode p_mode, Vis return ""; } +void VisualShaderNodeCustom::set_input_port_default_value(int p_port, const Variant &p_value) { + if (!is_initialized) { + VisualShaderNode::set_input_port_default_value(p_port, p_value); + } +} + +void VisualShaderNodeCustom::set_default_input_values(const Array &p_values) { + if (!is_initialized) { + VisualShaderNode::set_default_input_values(p_values); + } +} + +void VisualShaderNodeCustom::remove_input_port_default_value(int p_port) { + if (!is_initialized) { + VisualShaderNode::remove_input_port_default_value(p_port); + } +} + +void VisualShaderNodeCustom::clear_default_input_values() { + if (!is_initialized) { + VisualShaderNode::clear_default_input_values(); + } +} + +void VisualShaderNodeCustom::_set_input_port_default_value(int p_port, const Variant &p_value) { + VisualShaderNode::set_input_port_default_value(p_port, p_value); +} + +bool VisualShaderNodeCustom::_is_initialized() { + return is_initialized; +} + +void VisualShaderNodeCustom::_set_initialized(bool p_enabled) { + is_initialized = p_enabled; +} + void VisualShaderNodeCustom::_bind_methods() { BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_name")); BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_description")); @@ -314,6 +429,12 @@ void VisualShaderNodeCustom::_bind_methods() { BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_code", PropertyInfo(Variant::ARRAY, "input_vars"), PropertyInfo(Variant::ARRAY, "output_vars"), PropertyInfo(Variant::INT, "mode"), PropertyInfo(Variant::INT, "type"))); BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_global_code", PropertyInfo(Variant::INT, "mode"))); BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_highend")); + + ClassDB::bind_method(D_METHOD("_set_initialized", "enabled"), &VisualShaderNodeCustom::_set_initialized); + ClassDB::bind_method(D_METHOD("_is_initialized"), &VisualShaderNodeCustom::_is_initialized); + ClassDB::bind_method(D_METHOD("_set_input_port_default_value", "port", "value"), &VisualShaderNodeCustom::_set_input_port_default_value); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "initialized", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_initialized", "_is_initialized"); } VisualShaderNodeCustom::VisualShaderNodeCustom() { @@ -496,7 +617,7 @@ void VisualShader::replace_node(Type p_type, int p_id, const StringName &p_new_c if (g->nodes[p_id].node->get_class_name() == p_new_class) { return; } - VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instance(p_new_class)); + VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instantiate(p_new_class)); vsn->connect("changed", callable_mp(this, &VisualShader::_queue_update)); g->nodes[p_id].node = Ref<VisualShaderNode>(vsn); @@ -546,7 +667,7 @@ bool VisualShader::can_connect_nodes(Type p_type, int p_from_node, int p_from_po return false; } - if (p_from_port < 0 || p_from_port >= g->nodes[p_from_node].node->get_output_port_count()) { + if (p_from_port < 0 || p_from_port >= g->nodes[p_from_node].node->get_expanded_output_port_count()) { return false; } @@ -587,7 +708,7 @@ void VisualShader::connect_nodes_forced(Type p_type, int p_from_node, int p_from Graph *g = &graph[p_type]; ERR_FAIL_COND(!g->nodes.has(p_from_node)); - ERR_FAIL_INDEX(p_from_port, g->nodes[p_from_node].node->get_output_port_count()); + ERR_FAIL_INDEX(p_from_port, g->nodes[p_from_node].node->get_expanded_output_port_count()); ERR_FAIL_COND(!g->nodes.has(p_to_node)); ERR_FAIL_INDEX(p_to_port, g->nodes[p_to_node].node->get_input_port_count()); @@ -609,7 +730,7 @@ Error VisualShader::connect_nodes(Type p_type, int p_from_node, int p_from_port, Graph *g = &graph[p_type]; ERR_FAIL_COND_V(!g->nodes.has(p_from_node), ERR_INVALID_PARAMETER); - ERR_FAIL_INDEX_V(p_from_port, g->nodes[p_from_node].node->get_output_port_count(), ERR_INVALID_PARAMETER); + ERR_FAIL_INDEX_V(p_from_port, g->nodes[p_from_node].node->get_expanded_output_port_count(), ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(!g->nodes.has(p_to_node), ERR_INVALID_PARAMETER); ERR_FAIL_INDEX_V(p_to_port, g->nodes[p_to_node].node->get_input_port_count(), ERR_INVALID_PARAMETER); @@ -760,7 +881,7 @@ bool VisualShader::is_text_shader() const { String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port, Vector<DefaultTextureParam> &default_tex_params) const { Ref<VisualShaderNode> node = get_node(p_type, p_node); ERR_FAIL_COND_V(!node.is_valid(), String()); - ERR_FAIL_COND_V(p_port < 0 || p_port >= node->get_output_port_count(), String()); + ERR_FAIL_COND_V(p_port < 0 || p_port >= node->get_expanded_output_port_count(), String()); ERR_FAIL_COND_V(node->get_output_port_type(p_port) == VisualShaderNode::PORT_TYPE_TRANSFORM, String()); StringBuilder global_code; @@ -959,9 +1080,12 @@ static const char *type_string[VisualShader::TYPE_MAX] = { "vertex", "fragment", "light", - "emit", + "start", "process", - "end" + "collide", + "start_custom", + "process_custom", + "sky", }; bool VisualShader::_set(const StringName &p_name, const Variant &p_value) { @@ -1176,6 +1300,12 @@ void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const { Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBuilder &global_code_per_node, Map<Type, StringBuilder> &global_code_per_func, StringBuilder &code, Vector<VisualShader::DefaultTextureParam> &def_tex_params, const VMap<ConnectionKey, const List<Connection>::Element *> &input_connections, const VMap<ConnectionKey, const List<Connection>::Element *> &output_connections, int node, Set<int> &processed, bool for_preview, Set<StringName> &r_classes) const { const Ref<VisualShaderNode> vsnode = graph[type].nodes[node].node; + if (vsnode->is_disabled()) { + code += "// " + vsnode->get_caption() + ":" + itos(node) + "\n"; + code += "\t// Node is disabled and code is not generated.\n"; + return OK; + } + //check inputs recursively first int input_count = vsnode->get_input_port_count(); for (int i = 0; i < input_count; i++) { @@ -1230,7 +1360,8 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui return OK; } - code += "// " + vsnode->get_caption() + ":" + itos(node) + "\n"; + String node_name = "// " + vsnode->get_caption() + ":" + itos(node) + "\n"; + String node_code; Vector<String> input_vars; input_vars.resize(vsnode->get_input_port_count()); @@ -1244,6 +1375,11 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui if (input_connections.has(ck)) { //connected to something, use that output int from_node = input_connections[ck]->get().from_node; + + if (graph[type].nodes[from_node].node->is_disabled()) { + continue; + } + int from_port = input_connections[ck]->get().from_port; VisualShaderNode::PortType in_type = vsnode->get_input_port_type(i); @@ -1296,21 +1432,21 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui if (defval.get_type() == Variant::FLOAT) { float val = defval; inputs[i] = "n_in" + itos(node) + "p" + itos(i); - code += "\tfloat " + inputs[i] + " = " + vformat("%.5f", val) + ";\n"; + node_code += "\tfloat " + inputs[i] + " = " + vformat("%.5f", val) + ";\n"; } else if (defval.get_type() == Variant::INT) { int val = defval; inputs[i] = "n_in" + itos(node) + "p" + itos(i); - code += "\tint " + inputs[i] + " = " + itos(val) + ";\n"; + node_code += "\tint " + inputs[i] + " = " + itos(val) + ";\n"; } else if (defval.get_type() == Variant::BOOL) { bool val = defval; inputs[i] = "n_in" + itos(node) + "p" + itos(i); - code += "\tbool " + inputs[i] + " = " + (val ? "true" : "false") + ";\n"; + node_code += "\tbool " + inputs[i] + " = " + (val ? "true" : "false") + ";\n"; } else if (defval.get_type() == Variant::VECTOR3) { Vector3 val = defval; inputs[i] = "n_in" + itos(node) + "p" + itos(i); - code += "\tvec3 " + inputs[i] + " = " + vformat("vec3(%.5f, %.5f, %.5f);\n", val.x, val.y, val.z); - } else if (defval.get_type() == Variant::TRANSFORM) { - Transform val = defval; + node_code += "\tvec3 " + inputs[i] + " = " + vformat("vec3(%.5f, %.5f, %.5f);\n", val.x, val.y, val.z); + } else if (defval.get_type() == Variant::TRANSFORM3D) { + Transform3D val = defval; val.basis.transpose(); inputs[i] = "n_in" + itos(node) + "p" + itos(i); Array values; @@ -1323,7 +1459,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui values.push_back(val.origin.y); values.push_back(val.origin.z); bool err = false; - code += "\tmat4 " + inputs[i] + " = " + String("mat4(vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 1.0));\n").sprintf(values, &err); + node_code += "\tmat4 " + inputs[i] + " = " + String("mat4(vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 1.0));\n").sprintf(values, &err); } else { //will go empty, node is expected to know what it is doing at this point and handle it } @@ -1331,13 +1467,30 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui } int output_count = vsnode->get_output_port_count(); + int initial_output_count = output_count; + + Map<int, bool> expanded_output_ports; + + for (int i = 0; i < initial_output_count; i++) { + bool expanded = false; + + if (vsnode->is_output_port_expandable(i) && vsnode->_is_output_port_expanded(i)) { + expanded = true; + + if (vsnode->get_output_port_type(i) == VisualShaderNode::PORT_TYPE_VECTOR) { + output_count += 3; + } + } + expanded_output_ports.insert(i, expanded); + } + Vector<String> output_vars; - output_vars.resize(vsnode->get_output_port_count()); + output_vars.resize(output_count); String *outputs = output_vars.ptrw(); if (vsnode->is_simple_decl()) { // less code to generate for some simple_decl nodes - for (int i = 0; i < output_count; i++) { - String var_name = "n_out" + itos(node) + "p" + itos(i); + for (int i = 0, j = 0; i < initial_output_count; i++, j++) { + String var_name = "n_out" + itos(node) + "p" + itos(j); switch (vsnode->get_output_port_type(i)) { case VisualShaderNode::PORT_TYPE_SCALAR: outputs[i] = "float " + var_name; @@ -1357,34 +1510,88 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui default: { } } + if (expanded_output_ports[i]) { + if (vsnode->get_output_port_type(i) == VisualShaderNode::PORT_TYPE_VECTOR) { + j += 3; + } + } } } else { - for (int i = 0; i < output_count; i++) { - outputs[i] = "n_out" + itos(node) + "p" + itos(i); + for (int i = 0, j = 0; i < initial_output_count; i++, j++) { + outputs[i] = "n_out" + itos(node) + "p" + itos(j); switch (vsnode->get_output_port_type(i)) { case VisualShaderNode::PORT_TYPE_SCALAR: - code += String() + "\tfloat " + outputs[i] + ";\n"; + code += "\tfloat " + outputs[i] + ";\n"; break; case VisualShaderNode::PORT_TYPE_SCALAR_INT: - code += String() + "\tint " + outputs[i] + ";\n"; + code += "\tint " + outputs[i] + ";\n"; break; case VisualShaderNode::PORT_TYPE_VECTOR: - code += String() + "\tvec3 " + outputs[i] + ";\n"; + code += "\tvec3 " + outputs[i] + ";\n"; break; case VisualShaderNode::PORT_TYPE_BOOLEAN: - code += String() + "\tbool " + outputs[i] + ";\n"; + code += "\tbool " + outputs[i] + ";\n"; break; case VisualShaderNode::PORT_TYPE_TRANSFORM: - code += String() + "\tmat4 " + outputs[i] + ";\n"; + code += "\tmat4 " + outputs[i] + ";\n"; break; default: { } } + if (expanded_output_ports[i]) { + if (vsnode->get_output_port_type(i) == VisualShaderNode::PORT_TYPE_VECTOR) { + j += 3; + } + } } } - code += vsnode->generate_code(get_mode(), type, node, inputs, outputs, for_preview); + node_code += vsnode->generate_code(get_mode(), type, node, inputs, outputs, for_preview); + if (node_code != String()) { + code += node_name; + code += node_code; + code += "\n"; + } + + for (int i = 0; i < output_count; i++) { + bool new_line_inserted = false; + if (expanded_output_ports[i]) { + if (vsnode->get_output_port_type(i) == VisualShaderNode::PORT_TYPE_VECTOR) { + if (vsnode->is_output_port_connected(i + 1) || (for_preview && vsnode->get_output_port_for_preview() == (i + 1))) { // red-component + if (!new_line_inserted) { + code += "\n"; + new_line_inserted = true; + } + String r = "n_out" + itos(node) + "p" + itos(i + 1); + code += "\tfloat " + r + " = n_out" + itos(node) + "p" + itos(i) + ".r;\n"; + outputs[i + 1] = r; + } + + if (vsnode->is_output_port_connected(i + 2) || (for_preview && vsnode->get_output_port_for_preview() == (i + 2))) { // green-component + if (!new_line_inserted) { + code += "\n"; + new_line_inserted = true; + } + String g = "n_out" + itos(node) + "p" + itos(i + 2); + code += "\tfloat " + g + " = n_out" + itos(node) + "p" + itos(i) + ".g;\n"; + outputs[i + 2] = g; + } + + if (vsnode->is_output_port_connected(i + 3) || (for_preview && vsnode->get_output_port_for_preview() == (i + 3))) { // blue-component + if (!new_line_inserted) { + code += "\n"; + new_line_inserted = true; + } + String b = "n_out" + itos(node) + "p" + itos(i + 3); + code += "\tfloat " + b + " = n_out" + itos(node) + "p" + itos(i) + ".b;\n"; + outputs[i + 3] = b; + } + + i += 3; + } + } + } code += "\n"; // processed.insert(node); @@ -1395,7 +1602,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui bool VisualShader::has_func_name(RenderingServer::ShaderMode p_mode, const String &p_func_name) const { if (!ShaderTypes::get_singleton()->get_functions(p_mode).has(p_func_name)) { if (p_mode == RenderingServer::ShaderMode::SHADER_PARTICLES) { - if (p_func_name == "emit" || p_func_name == "process" || p_func_name == "end") { + if (p_func_name == "start_custom" || p_func_name == "process_custom" || p_func_name == "collide") { return true; } } @@ -1476,11 +1683,12 @@ void VisualShader::_update_shader() const { global_code += "render_mode " + render_mode + ";\n\n"; } - static const char *func_name[TYPE_MAX] = { "vertex", "fragment", "light", "emit", "process", "end" }; + static const char *func_name[TYPE_MAX] = { "vertex", "fragment", "light", "start", "process", "collide", "start_custom", "process_custom", "sky" }; String global_expressions; Set<String> used_uniform_names; List<VisualShaderNodeUniform *> uniforms; + Map<int, List<int>> emitters; for (int i = 0, index = 0; i < TYPE_MAX; i++) { if (!has_func_name(RenderingServer::ShaderMode(shader_mode), func_name[i])) { @@ -1505,6 +1713,19 @@ void VisualShader::_update_shader() const { if (uniform.is_valid()) { uniforms.push_back(uniform.ptr()); } + Ref<VisualShaderNodeParticleEmit> emit_particle = Object::cast_to<VisualShaderNodeParticleEmit>(E->get().node.ptr()); + if (emit_particle.is_valid()) { + if (!emitters.has(i)) { + emitters.insert(i, List<int>()); + } + + for (Map<int, Node>::Element *M = graph[i].nodes.front(); M; M = M->next()) { + if (M->get().node == emit_particle.ptr()) { + emitters[i].push_back(M->key()); + break; + } + } + } } } @@ -1553,6 +1774,13 @@ void VisualShader::_update_shader() const { Error err = _write_node(Type(i), global_code, global_code_per_node, global_code_per_func, func_code, default_tex_params, input_connections, output_connections, NODE_ID_OUTPUT, processed, false, classes); ERR_FAIL_COND(err != OK); + if (emitters.has(i)) { + for (List<int>::Element *E = emitters[i].front(); E; E = E->next()) { + err = _write_node(Type(i), global_code, global_code_per_node, global_code_per_func, func_code, default_tex_params, input_connections, output_connections, E->get(), processed, false, classes); + ERR_FAIL_COND(err != OK); + } + } + if (shader_mode == Shader::MODE_PARTICLES) { code_map.insert(i, func_code); } else { @@ -1561,19 +1789,130 @@ void VisualShader::_update_shader() const { } } + String global_compute_code; + if (shader_mode == Shader::MODE_PARTICLES) { - code += "\nvoid compute() {\n"; - code += "\tif (RESTART) {\n"; - code += code_map[TYPE_EMIT]; - code += "\t} else {\n"; - code += code_map[TYPE_PROCESS]; + bool has_start = !code_map[TYPE_START].is_empty(); + bool has_start_custom = !code_map[TYPE_START_CUSTOM].is_empty(); + bool has_process = !code_map[TYPE_PROCESS].is_empty(); + bool has_process_custom = !code_map[TYPE_PROCESS_CUSTOM].is_empty(); + bool has_collide = !code_map[TYPE_COLLIDE].is_empty(); + + code += "void start() {\n"; + if (has_start || has_start_custom) { + code += "\tuint __seed = __hash(NUMBER + uint(1) + RANDOM_SEED);\n"; + code += "\tvec3 __diff = TRANSFORM[3].xyz - EMISSION_TRANSFORM[3].xyz;\n"; + code += "\tfloat __radians;\n"; + code += "\tvec3 __vec3_buff1;\n"; + code += "\tvec3 __vec3_buff2;\n"; + code += "\tfloat __scalar_buff1;\n"; + code += "\tfloat __scalar_buff2;\n"; + code += "\tvec3 __ndiff = normalize(__diff);\n\n"; + } + if (has_start) { + code += "\t{\n"; + code += code_map[TYPE_START].replace("\n\t", "\n\t\t"); + code += "\t}\n"; + if (has_start_custom) { + code += "\t\n"; + } + } + if (has_start_custom) { + code += "\t{\n"; + code += code_map[TYPE_START_CUSTOM].replace("\n\t", "\n\t\t"); + code += "\t}\n"; + } + code += "}\n\n"; + code += "void process() {\n"; + if (has_process || has_process_custom || has_collide) { + code += "\tuint __seed = __hash(NUMBER + uint(1) + RANDOM_SEED);\n"; + code += "\tvec3 __vec3_buff1;\n"; + code += "\tvec3 __diff = TRANSFORM[3].xyz - EMISSION_TRANSFORM[3].xyz;\n"; + code += "\tvec3 __ndiff = normalize(__diff);\n\n"; + } + code += "\t{\n"; + String tab = "\t"; + if (has_collide) { + code += "\t\tif (COLLIDED) {\n\n"; + code += code_map[TYPE_COLLIDE].replace("\n\t", "\n\t\t\t"); + if (has_process) { + code += "\t\t} else {\n\n"; + tab += "\t"; + } + } + if (has_process) { + code += code_map[TYPE_PROCESS].replace("\n\t", "\n\t" + tab); + } + if (has_collide) { + code += "\t\t}\n"; + } code += "\t}\n"; - code += "}\n"; + + if (has_process_custom) { + code += "\t{\n\n"; + code += code_map[TYPE_PROCESS_CUSTOM].replace("\n\t", "\n\t\t"); + code += "\t}\n"; + } + + code += "}\n\n"; + + global_compute_code += "float __rand_from_seed(inout uint seed) {\n"; + global_compute_code += "\tint k;\n"; + global_compute_code += "\tint s = int(seed);\n"; + global_compute_code += "\tif (s == 0)\n"; + global_compute_code += "\ts = 305420679;\n"; + global_compute_code += "\tk = s / 127773;\n"; + global_compute_code += "\ts = 16807 * (s - k * 127773) - 2836 * k;\n"; + global_compute_code += "\tif (s < 0)\n"; + global_compute_code += "\t\ts += 2147483647;\n"; + global_compute_code += "\tseed = uint(s);\n"; + global_compute_code += "\treturn float(seed % uint(65536)) / 65535.0;\n"; + global_compute_code += "}\n\n"; + + global_compute_code += "float __rand_from_seed_m1_p1(inout uint seed) {\n"; + global_compute_code += "\treturn __rand_from_seed(seed) * 2.0 - 1.0;\n"; + global_compute_code += "}\n\n"; + + global_compute_code += "float __randf_range(inout uint seed, float from, float to) {\n"; + global_compute_code += "\treturn __rand_from_seed(seed) * (to - from) + from;\n"; + global_compute_code += "}\n\n"; + + global_compute_code += "vec3 __randv_range(inout uint seed, vec3 from, vec3 to) {\n"; + global_compute_code += "\treturn vec3(__randf_range(seed, from.x, to.x), __randf_range(seed, from.y, to.y), __randf_range(seed, from.z, to.z));\n"; + global_compute_code += "}\n\n"; + + global_compute_code += "uint __hash(uint x) {\n"; + global_compute_code += "\tx = ((x >> uint(16)) ^ x) * uint(73244475);\n"; + global_compute_code += "\tx = ((x >> uint(16)) ^ x) * uint(73244475);\n"; + global_compute_code += "\tx = (x >> uint(16)) ^ x;\n"; + global_compute_code += "\treturn x;\n"; + global_compute_code += "}\n\n"; + + global_compute_code += "mat3 __build_rotation_mat3(vec3 axis, float angle) {\n"; + global_compute_code += "\taxis = normalize(axis);\n"; + global_compute_code += "\tfloat s = sin(angle);\n"; + global_compute_code += "\tfloat c = cos(angle);\n"; + global_compute_code += "\tfloat oc = 1.0 - c;\n"; + global_compute_code += "\treturn mat3(vec3(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s), vec3(oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s), vec3(oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c));\n"; + global_compute_code += "}\n\n"; + + global_compute_code += "mat4 __build_rotation_mat4(vec3 axis, float angle) {\n"; + global_compute_code += "\taxis = normalize(axis);\n"; + global_compute_code += "\tfloat s = sin(angle);\n"; + global_compute_code += "\tfloat c = cos(angle);\n"; + global_compute_code += "\tfloat oc = 1.0 - c;\n"; + global_compute_code += "\treturn mat4(vec4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0), vec4(oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0), vec4(oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0), vec4(0, 0, 0, 1));\n"; + global_compute_code += "}\n\n"; + + global_compute_code += "vec3 __get_random_unit_vec3(inout uint seed) {\n"; + global_compute_code += "\treturn normalize(vec3(__rand_from_seed_m1_p1(seed), __rand_from_seed_m1_p1(seed), __rand_from_seed_m1_p1(seed)));\n"; + global_compute_code += "}\n\n"; } //set code secretly global_code += "\n\n"; String final_code = global_code; + final_code += global_compute_code; final_code += global_code_per_node; final_code += global_expressions; String tcode = code; @@ -1664,9 +2003,12 @@ void VisualShader::_bind_methods() { BIND_ENUM_CONSTANT(TYPE_VERTEX); BIND_ENUM_CONSTANT(TYPE_FRAGMENT); BIND_ENUM_CONSTANT(TYPE_LIGHT); - BIND_ENUM_CONSTANT(TYPE_EMIT); + BIND_ENUM_CONSTANT(TYPE_START); BIND_ENUM_CONSTANT(TYPE_PROCESS); - BIND_ENUM_CONSTANT(TYPE_END); + BIND_ENUM_CONSTANT(TYPE_COLLIDE); + BIND_ENUM_CONSTANT(TYPE_START_CUSTOM); + BIND_ENUM_CONSTANT(TYPE_PROCESS_CUSTOM); + BIND_ENUM_CONSTANT(TYPE_SKY); BIND_ENUM_CONSTANT(TYPE_MAX); BIND_CONSTANT(NODE_ID_INVALID); @@ -1676,11 +2018,20 @@ void VisualShader::_bind_methods() { VisualShader::VisualShader() { dirty.set(); for (int i = 0; i < TYPE_MAX; i++) { - Ref<VisualShaderNodeOutput> output; - output.instance(); - output->shader_type = Type(i); - output->shader_mode = shader_mode; - graph[i].nodes[NODE_ID_OUTPUT].node = output; + if (i > (int)TYPE_LIGHT && i < (int)TYPE_SKY) { + Ref<VisualShaderNodeParticleOutput> output; + output.instantiate(); + output->shader_type = Type(i); + output->shader_mode = shader_mode; + graph[i].nodes[NODE_ID_OUTPUT].node = output; + } else { + Ref<VisualShaderNodeOutput> output; + output.instantiate(); + output->shader_type = Type(i); + output->shader_mode = shader_mode; + graph[i].nodes[NODE_ID_OUTPUT].node = output; + } + graph[i].nodes[NODE_ID_OUTPUT].position = Vector2(400, 150); } } @@ -1698,7 +2049,6 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "point_size", "POINT_SIZE" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "world", "WORLD_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "modelview", "MODELVIEW_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "camera", "CAMERA_MATRIX" }, @@ -1721,10 +2071,8 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "point_coord", "vec3(POINT_COORD, 0.0)" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV, 0.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "side", "float(FRONT_FACING ? 1.0 : 0.0)" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "world", "WORLD_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_camera", "INV_CAMERA_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "camera", "CAMERA_MATRIX" }, @@ -1750,7 +2098,6 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "specular", "SPECULAR_LIGHT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "metallic", "METALLIC" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "world", "WORLD_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_camera", "INV_CAMERA_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "camera", "CAMERA_MATRIX" }, @@ -1759,6 +2106,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "viewport_size", "vec3(VIEWPORT_SIZE, 0.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_BOOLEAN, "output_is_srgb", "OUTPUT_IS_SRGB" }, + // Canvas Item, Vertex { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "vec3(VERTEX, 0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV, 0.0)" }, @@ -1766,12 +2114,12 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "point_size", "POINT_SIZE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "texture_pixel_size", "vec3(TEXTURE_PIXEL_SIZE, 1.0)" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "world", "WORLD_MATRIX" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "canvas", "CANVAS_MATRIX" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "screen", "SCREEN_MATRIX" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_light_pass", "AT_LIGHT_PASS" }, + // Canvas Item, Fragment { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.xyz" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV, 0.0)" }, @@ -1789,6 +2137,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "specular_shininess", "SPECULAR_SHININESS.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "specular_shininess_alpha", "SPECULAR_SHININESS.a" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "specular_shininess_texture", "SPECULAR_SHININESS_TEXTURE" }, + // Canvas Item, Light { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.xyz" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV, 0.0)" }, @@ -1811,27 +2160,45 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "specular_shininess", "SPECULAR_SHININESS.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "specular_shininess_alpha", "SPECULAR_SHININESS.a" }, - // Particles, Emit - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR, "restart", "float(RESTART ? 1.0 : 0.0)" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR, "active", "float(ACTIVE ? 1.0 : 0.0)" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR_INT, "index", "INDEX" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + // Particles, Start + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR, "attractor_force", "ATTRACTOR_FORCE" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR_INT, "index", "INDEX" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + + // Particles, Start (Custom) + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "attractor_force", "ATTRACTOR_FORCE" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR_INT, "index", "INDEX" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Particles, Process + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR, "attractor_force", "ATTRACTOR_FORCE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "restart", "float(RESTART ? 1.0 : 0.0)" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "active", "float(ACTIVE ? 1.0 : 0.0)" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, @@ -1841,51 +2208,70 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, - // Particles, End - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR, "restart", "float(RESTART ? 1.0 : 0.0)" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR, "active", "float(ACTIVE ? 1.0 : 0.0)" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR_INT, "index", "INDEX" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, - - // Sky, Fragment - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_cubemap_pass", "AT_CUBEMAP_PASS" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_half_res_pass", "AT_HALF_RES_PASS" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_quarter_res_pass", "AT_QUARTER_RES_PASS" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "eyedir", "EYEDIR" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "half_res_color", "HALF_RES_COLOR.rgb" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "half_res_alpha", "HALF_RES_COLOR.a" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "light0_color", "LIGHT0_COLOR" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "light0_direction", "LIGHT0_DIRECTION" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_BOOLEAN, "light0_enabled", "LIGHT0_ENABLED" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "light0_energy", "LIGHT0_ENERGY" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "light1_color", "LIGHT1_COLOR" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "light1_direction", "LIGHT1_DIRECTION" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_BOOLEAN, "light1_enabled", "LIGHT1_ENABLED" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "light1_energy", "LIGHT1_ENERGY" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "light2_color", "LIGHT2_COLOR" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "light2_direction", "LIGHT2_DIRECTION" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_BOOLEAN, "light2_enabled", "LIGHT2_ENABLED" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "light2_energy", "LIGHT2_ENERGY" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "light3_color", "LIGHT3_COLOR" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "light3_direction", "LIGHT3_DIRECTION" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_BOOLEAN, "light3_enabled", "LIGHT3_ENABLED" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "light3_energy", "LIGHT3_ENERGY" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "position", "POSITION" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "quarter_res_color", "QUARTER_RES_COLOR.rgb" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "quarter_res_alpha", "QUARTER_RES_COLOR.a" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "radiance", "RADIANCE" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV, 0.0)" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "sky_coords", "vec3(SKY_COORDS, 0.0)" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + // Particles, Process (Custom) + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "attractor_force", "ATTRACTOR_FORCE" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR_INT, "index", "INDEX" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + + // Particles, Collide + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR, "attractor_force", "ATTRACTOR_FORCE" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "collision_depth", "COLLISION_DEPTH" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR, "collision_normal", "COLLISION_NORMAL" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR_INT, "index", "INDEX" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + + // Sky, Sky + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_cubemap_pass", "AT_CUBEMAP_PASS" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_half_res_pass", "AT_HALF_RES_PASS" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_quarter_res_pass", "AT_QUARTER_RES_PASS" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "eyedir", "EYEDIR" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "half_res_color", "HALF_RES_COLOR.rgb" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "half_res_alpha", "HALF_RES_COLOR.a" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "light0_color", "LIGHT0_COLOR" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "light0_direction", "LIGHT0_DIRECTION" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "light0_enabled", "LIGHT0_ENABLED" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "light0_energy", "LIGHT0_ENERGY" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "light1_color", "LIGHT1_COLOR" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "light1_direction", "LIGHT1_DIRECTION" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "light1_enabled", "LIGHT1_ENABLED" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "light1_energy", "LIGHT1_ENERGY" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "light2_color", "LIGHT2_COLOR" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "light2_direction", "LIGHT2_DIRECTION" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "light2_enabled", "LIGHT2_ENABLED" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "light2_energy", "LIGHT2_ENERGY" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "light3_color", "LIGHT3_COLOR" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "light3_direction", "LIGHT3_DIRECTION" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "light3_enabled", "LIGHT3_ENABLED" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "light3_energy", "LIGHT3_ENERGY" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "position", "POSITION" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "quarter_res_color", "QUARTER_RES_COLOR.rgb" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "quarter_res_alpha", "QUARTER_RES_COLOR.a" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SAMPLER, "radiance", "RADIANCE" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV, 0.0)" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "sky_coords", "vec3(SKY_COORDS, 0.0)" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, { Shader::MODE_MAX, VisualShader::TYPE_MAX, VisualShaderNode::PORT_TYPE_TRANSFORM, nullptr, nullptr }, }; @@ -1930,11 +2316,13 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV, 0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, - // Particles, Vertex - { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "vec3(0.0, 0.0, 1.0)" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + + // Particles + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, { Shader::MODE_MAX, VisualShader::TYPE_MAX, VisualShaderNode::PORT_TYPE_TRANSFORM, nullptr, nullptr }, }; @@ -2145,6 +2533,14 @@ Vector<StringName> VisualShaderNodeInput::get_editable_properties() const { return props; } +void VisualShaderNodeInput::set_shader_type(VisualShader::Type p_shader_type) { + shader_type = p_shader_type; +} + +void VisualShaderNodeInput::set_shader_mode(Shader::Mode p_shader_mode) { + shader_mode = p_shader_mode; +} + void VisualShaderNodeInput::_bind_methods() { ClassDB::bind_method(D_METHOD("set_input_name", "name"), &VisualShaderNodeInput::set_input_name); ClassDB::bind_method(D_METHOD("get_input_name"), &VisualShaderNodeInput::get_input_name); @@ -2312,6 +2708,28 @@ VisualShaderNodeUniformRef::UniformType VisualShaderNodeUniformRef::get_uniform_ return UniformType::UNIFORM_TYPE_FLOAT; } +VisualShaderNodeUniformRef::PortType VisualShaderNodeUniformRef::get_port_type_by_index(int p_idx) const { + if (p_idx >= 0 && p_idx < uniforms.size()) { + switch (uniforms[p_idx].type) { + case UniformType::UNIFORM_TYPE_FLOAT: + return PORT_TYPE_SCALAR; + case UniformType::UNIFORM_TYPE_INT: + return PORT_TYPE_SCALAR_INT; + case UniformType::UNIFORM_TYPE_SAMPLER: + return PORT_TYPE_SAMPLER; + case UniformType::UNIFORM_TYPE_VECTOR: + return PORT_TYPE_VECTOR; + case UniformType::UNIFORM_TYPE_TRANSFORM: + return PORT_TYPE_TRANSFORM; + case UniformType::UNIFORM_TYPE_COLOR: + return PORT_TYPE_VECTOR; + default: + break; + } + } + return PORT_TYPE_SCALAR; +} + String VisualShaderNodeUniformRef::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { switch (uniform_type) { case UniformType::UNIFORM_TYPE_FLOAT: @@ -2382,6 +2800,8 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "model_view_matrix", "MODELVIEW_MATRIX" }, + // Spatial, Fragment { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "albedo", "ALBEDO" }, @@ -2425,33 +2845,10 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = { // Canvas Item, Light { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light", "LIGHT.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "light_alpha", "LIGHT.a" }, - // Particles, Emit - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, - // Particles, Process - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, - // Particles, End - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, - // Sky, Fragment - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR" }, - { Shader::MODE_SKY, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "ALPHA" }, + + // Sky, Sky + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "ALPHA" }, { Shader::MODE_MAX, VisualShader::TYPE_MAX, VisualShaderNode::PORT_TYPE_TRANSFORM, nullptr, nullptr }, }; @@ -2521,9 +2918,13 @@ String VisualShaderNodeOutput::get_output_port_name(int p_port) const { } bool VisualShaderNodeOutput::is_port_separator(int p_index) const { + if (shader_mode == Shader::MODE_SPATIAL && shader_type == VisualShader::TYPE_VERTEX) { + String name = get_input_port_name(p_index); + return bool(name == "Model View Matrix"); + } if (shader_mode == Shader::MODE_SPATIAL && shader_type == VisualShader::TYPE_FRAGMENT) { String name = get_input_port_name(p_index); - return (name == "Normal" || name == "Rim" || name == "Alpha Scissor Threshold"); + return bool(name == "Normal" || name == "Rim" || name == "Alpha Scissor Threshold"); } return false; } @@ -2667,6 +3068,70 @@ VisualShaderNodeResizableBase::VisualShaderNodeResizableBase() { set_allow_v_resize(true); } +////////////// Comment + +String VisualShaderNodeComment::get_caption() const { + return title; +} + +int VisualShaderNodeComment::get_input_port_count() const { + return 0; +} + +VisualShaderNodeComment::PortType VisualShaderNodeComment::get_input_port_type(int p_port) const { + return PortType::PORT_TYPE_SCALAR; +} + +String VisualShaderNodeComment::get_input_port_name(int p_port) const { + return String(); +} + +int VisualShaderNodeComment::get_output_port_count() const { + return 0; +} + +VisualShaderNodeComment::PortType VisualShaderNodeComment::get_output_port_type(int p_port) const { + return PortType::PORT_TYPE_SCALAR; +} + +String VisualShaderNodeComment::get_output_port_name(int p_port) const { + return String(); +} + +void VisualShaderNodeComment::set_title(const String &p_title) { + title = p_title; +} + +String VisualShaderNodeComment::get_title() const { + return title; +} + +void VisualShaderNodeComment::set_description(const String &p_description) { + description = p_description; +} + +String VisualShaderNodeComment::get_description() const { + return description; +} + +String VisualShaderNodeComment::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + return String(); +} + +void VisualShaderNodeComment::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_title", "title"), &VisualShaderNodeComment::set_title); + ClassDB::bind_method(D_METHOD("get_title"), &VisualShaderNodeComment::get_title); + + ClassDB::bind_method(D_METHOD("set_description", "description"), &VisualShaderNodeComment::set_description); + ClassDB::bind_method(D_METHOD("get_description"), &VisualShaderNodeComment::get_description); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "description"), "set_description", "get_description"); +} + +VisualShaderNodeComment::VisualShaderNodeComment() { +} + ////////////// GroupBase void VisualShaderNodeGroupBase::set_inputs(const String &p_inputs) { @@ -3054,11 +3519,11 @@ int VisualShaderNodeGroupBase::get_free_output_port_id() const { return output_ports.size(); } -void VisualShaderNodeGroupBase::set_control(Control *p_control, int p_index) { +void VisualShaderNodeGroupBase::set_ctrl_pressed(Control *p_control, int p_index) { controls[p_index] = p_control; } -Control *VisualShaderNodeGroupBase::get_control(int p_index) { +Control *VisualShaderNodeGroupBase::is_ctrl_pressed(int p_index) { ERR_FAIL_COND_V(!controls.has(p_index), nullptr); return controls[p_index]; } diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index ef724c7650..880c401b29 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -51,9 +51,12 @@ public: TYPE_VERTEX, TYPE_FRAGMENT, TYPE_LIGHT, - TYPE_EMIT, + TYPE_START, TYPE_PROCESS, - TYPE_END, + TYPE_COLLIDE, + TYPE_START_CUSTOM, + TYPE_PROCESS_CUSTOM, + TYPE_SKY, TYPE_MAX }; @@ -198,9 +201,12 @@ class VisualShaderNode : public Resource { Map<int, Variant> default_input_values; Map<int, bool> connected_input_ports; Map<int, int> connected_output_ports; + Map<int, bool> expanded_output_ports; protected: bool simple_decl = true; + bool disabled = false; + static void _bind_methods(); public: @@ -222,10 +228,12 @@ public: virtual PortType get_input_port_type(int p_port) const = 0; virtual String get_input_port_name(int p_port) const = 0; - void set_input_port_default_value(int p_port, const Variant &p_value); + virtual void set_input_port_default_value(int p_port, const Variant &p_value); Variant get_input_port_default_value(int p_port) const; // if NIL (default if node does not set anything) is returned, it means no default value is wanted if disconnected, thus no input var must be supplied (empty string will be supplied) Array get_default_input_values() const; - void set_default_input_values(const Array &p_values); + virtual void set_default_input_values(const Array &p_values); + virtual void remove_input_port_default_value(int p_port); + virtual void clear_default_input_values(); virtual int get_output_port_count() const = 0; virtual PortType get_output_port_type(int p_port) const = 0; @@ -244,17 +252,28 @@ public: void set_input_port_connected(int p_port, bool p_connected); virtual bool is_generate_input_var(int p_port) const; + virtual bool is_output_port_expandable(int p_port) const; + void _set_output_ports_expanded(const Array &p_data); + Array _get_output_ports_expanded() const; + void _set_output_port_expanded(int p_port, bool p_expanded); + bool _is_output_port_expanded(int p_port) const; + int get_expanded_output_port_count() const; + virtual bool is_code_generated() const; virtual bool is_show_prop_names() const; virtual bool is_use_prop_slots() const; + bool is_disabled() const; + void set_disabled(bool p_disabled = true); + virtual Vector<StringName> get_editable_properties() const; virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; virtual String generate_global_per_func(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const = 0; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + // If no output is connected, the output var passed will be empty. If no input is connected and input is NIL, the input var passed will be empty. + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const = 0; virtual String get_warning(Shader::Mode p_mode, VisualShader::Type p_type) const; @@ -271,6 +290,7 @@ class VisualShaderNodeCustom : public VisualShaderNode { int type = 0; }; + bool is_initialized = false; List<Port> input_ports; List<Port> output_ports; @@ -287,7 +307,14 @@ protected: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; + virtual void set_input_port_default_value(int p_port, const Variant &p_value) override; + virtual void set_default_input_values(const Array &p_values) override; + virtual void remove_input_port_default_value(int p_port) override; + virtual void clear_default_input_values() override; + protected: + void _set_input_port_default_value(int p_port, const Variant &p_value); + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; @@ -296,6 +323,9 @@ protected: public: VisualShaderNodeCustom(); void update_ports(); + + bool _is_initialized(); + void _set_initialized(bool p_enabled); }; ///// @@ -320,6 +350,10 @@ class VisualShaderNodeInput : public VisualShaderNode { String input_name = "[None]"; +public: + void set_shader_type(VisualShader::Type p_shader_type); + void set_shader_mode(Shader::Mode p_shader_mode); + protected: static void _bind_methods(); void _validate_property(PropertyInfo &property) const override; @@ -421,6 +455,7 @@ public: bool is_global_code_generated() const; virtual bool is_qualifier_supported(Qualifier p_qual) const = 0; + virtual bool is_convertible_to_constant() const = 0; virtual Vector<StringName> get_editable_properties() const override; virtual String get_warning(Shader::Mode p_mode, VisualShader::Type p_type) const override; @@ -482,6 +517,7 @@ public: String get_uniform_name_by_index(int p_idx) const; UniformType get_uniform_type_by_name(const String &p_name) const; UniformType get_uniform_type_by_index(int p_idx) const; + PortType get_port_type_by_index(int p_idx) const; virtual Vector<StringName> get_editable_properties() const override; @@ -510,6 +546,38 @@ public: VisualShaderNodeResizableBase(); }; +class VisualShaderNodeComment : public VisualShaderNodeResizableBase { + GDCLASS(VisualShaderNodeComment, VisualShaderNodeResizableBase); + +protected: + String title = "Comment"; + String description = ""; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + + virtual int get_output_port_count() const override; + virtual PortType get_output_port_type(int p_port) const override; + virtual String get_output_port_name(int p_port) const override; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + void set_title(const String &p_title); + String get_title() const; + + void set_description(const String &p_description); + String get_description() const; + + VisualShaderNodeComment(); +}; + class VisualShaderNodeGroupBase : public VisualShaderNodeResizableBase { GDCLASS(VisualShaderNodeGroupBase, VisualShaderNodeResizableBase); @@ -567,8 +635,8 @@ public: int get_free_input_port_id() const; int get_free_output_port_id() const; - void set_control(Control *p_control, int p_index); - Control *get_control(int p_index); + void set_ctrl_pressed(Control *p_control, int p_index); + Control *is_ctrl_pressed(int p_index); void set_editable(bool p_enabled); bool is_editable() const; diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index a99c09e89c..79ea9d72df 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -242,6 +242,13 @@ String VisualShaderNodeColorConstant::get_output_port_name(int p_port) const { return p_port == 0 ? "" : "alpha"; //no output port means the editor will be used as port } +bool VisualShaderNodeColorConstant::is_output_port_expandable(int p_port) const { + if (p_port == 0) { + return true; + } + return false; +} + String VisualShaderNodeColorConstant::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { String code; code += "\t" + p_output_vars[0] + " = " + vformat("vec3(%.6f, %.6f, %.6f)", constant.r, constant.g, constant.b) + ";\n"; @@ -334,10 +341,10 @@ void VisualShaderNodeVec3Constant::_bind_methods() { VisualShaderNodeVec3Constant::VisualShaderNodeVec3Constant() { } -////////////// Transform +////////////// Transform3D String VisualShaderNodeTransformConstant::get_caption() const { - return "Transform"; + return "Transform3D"; } int VisualShaderNodeTransformConstant::get_input_port_count() const { @@ -365,7 +372,7 @@ String VisualShaderNodeTransformConstant::get_output_port_name(int p_port) const } String VisualShaderNodeTransformConstant::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { - Transform t = constant; + Transform3D t = constant; t.basis.transpose(); String code = "\t" + p_output_vars[0] + " = mat4("; @@ -376,12 +383,12 @@ String VisualShaderNodeTransformConstant::generate_code(Shader::Mode p_mode, Vis return code; } -void VisualShaderNodeTransformConstant::set_constant(Transform p_value) { +void VisualShaderNodeTransformConstant::set_constant(Transform3D p_value) { constant = p_value; emit_changed(); } -Transform VisualShaderNodeTransformConstant::get_constant() const { +Transform3D VisualShaderNodeTransformConstant::get_constant() const { return constant; } @@ -395,7 +402,7 @@ void VisualShaderNodeTransformConstant::_bind_methods() { ClassDB::bind_method(D_METHOD("set_constant", "value"), &VisualShaderNodeTransformConstant::set_constant); ClassDB::bind_method(D_METHOD("get_constant"), &VisualShaderNodeTransformConstant::get_constant); - ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM, "constant"), "set_constant", "get_constant"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "constant"), "set_constant", "get_constant"); } VisualShaderNodeTransformConstant::VisualShaderNodeTransformConstant() { @@ -455,6 +462,13 @@ String VisualShaderNodeTexture::get_output_port_name(int p_port) const { return p_port == 0 ? "rgb" : "alpha"; } +bool VisualShaderNodeTexture::is_output_port_expandable(int p_port) const { + if (p_port == 0) { + return true; + } + return false; +} + String VisualShaderNodeTexture::get_input_port_default_hint(int p_port) const { if (p_port == 0) { return "default"; @@ -775,7 +789,7 @@ void VisualShaderNodeTexture::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "source", PROPERTY_HINT_ENUM, "Texture,Screen,Texture2D,NormalMap2D,Depth,SamplerPort"), "set_source", "get_source"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_type", PROPERTY_HINT_ENUM, "Data,Color,Normalmap"), "set_texture_type", "get_texture_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_type", PROPERTY_HINT_ENUM, "Data,Color,Normal Map"), "set_texture_type", "get_texture_type"); BIND_ENUM_CONSTANT(SOURCE_TEXTURE); BIND_ENUM_CONSTANT(SOURCE_SCREEN); @@ -791,7 +805,7 @@ void VisualShaderNodeTexture::_bind_methods() { VisualShaderNodeTexture::VisualShaderNodeTexture() { } -////////////// Curve +////////////// CurveTexture String VisualShaderNodeCurveTexture::get_caption() const { return "CurveTexture"; @@ -846,7 +860,7 @@ String VisualShaderNodeCurveTexture::generate_code(Shader::Mode p_mode, VisualSh } String id = make_unique_id(p_type, p_id, "curve"); String code; - code += "\t" + p_output_vars[0] + " = texture(" + id + ", vec2(" + p_input_vars[0] + ", 0.0)).r;\n"; + code += "\t" + p_output_vars[0] + " = texture(" + id + ", vec2(" + p_input_vars[0] + ")).r;\n"; return code; } @@ -875,6 +889,90 @@ VisualShaderNodeCurveTexture::VisualShaderNodeCurveTexture() { allow_v_resize = false; } +////////////// CurveXYZTexture + +String VisualShaderNodeCurveXYZTexture::get_caption() const { + return "CurveXYZTexture"; +} + +int VisualShaderNodeCurveXYZTexture::get_input_port_count() const { + return 1; +} + +VisualShaderNodeCurveXYZTexture::PortType VisualShaderNodeCurveXYZTexture::get_input_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeCurveXYZTexture::get_input_port_name(int p_port) const { + return String(); +} + +int VisualShaderNodeCurveXYZTexture::get_output_port_count() const { + return 1; +} + +VisualShaderNodeCurveXYZTexture::PortType VisualShaderNodeCurveXYZTexture::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeCurveXYZTexture::get_output_port_name(int p_port) const { + return String(); +} + +void VisualShaderNodeCurveXYZTexture::set_texture(Ref<CurveXYZTexture> p_texture) { + texture = p_texture; + emit_changed(); +} + +Ref<CurveXYZTexture> VisualShaderNodeCurveXYZTexture::get_texture() const { + return texture; +} + +Vector<StringName> VisualShaderNodeCurveXYZTexture::get_editable_properties() const { + Vector<StringName> props; + props.push_back("texture"); + return props; +} + +String VisualShaderNodeCurveXYZTexture::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + return "uniform sampler2D " + make_unique_id(p_type, p_id, "curve3d") + ";\n"; +} + +String VisualShaderNodeCurveXYZTexture::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + if (p_input_vars[0] == String()) { + return "\t" + p_output_vars[0] + " = vec3(0.0);\n"; + } + String id = make_unique_id(p_type, p_id, "curve3d"); + String code; + code += "\t" + p_output_vars[0] + " = texture(" + id + ", vec2(" + p_input_vars[0] + ")).rgb;\n"; + return code; +} + +Vector<VisualShader::DefaultTextureParam> VisualShaderNodeCurveXYZTexture::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { + VisualShader::DefaultTextureParam dtp; + dtp.name = make_unique_id(p_type, p_id, "curve3d"); + dtp.param = texture; + Vector<VisualShader::DefaultTextureParam> ret; + ret.push_back(dtp); + return ret; +} + +void VisualShaderNodeCurveXYZTexture::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_texture", "texture"), &VisualShaderNodeCurveXYZTexture::set_texture); + ClassDB::bind_method(D_METHOD("get_texture"), &VisualShaderNodeCurveXYZTexture::get_texture); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "CurveXYZTexture"), "set_texture", "get_texture"); +} + +bool VisualShaderNodeCurveXYZTexture::is_use_prop_slots() const { + return true; +} + +VisualShaderNodeCurveXYZTexture::VisualShaderNodeCurveXYZTexture() { + simple_decl = true; + allow_v_resize = false; +} + ////////////// Sample3D int VisualShaderNodeSample3D::get_input_port_count() const { @@ -917,6 +1015,13 @@ String VisualShaderNodeSample3D::get_output_port_name(int p_port) const { return p_port == 0 ? "rgb" : "alpha"; } +bool VisualShaderNodeSample3D::is_output_port_expandable(int p_port) const { + if (p_port == 0) { + return true; + } + return false; +} + String VisualShaderNodeSample3D::get_input_port_default_hint(int p_port) const { if (p_port == 0) { return "default"; @@ -1168,6 +1273,13 @@ String VisualShaderNodeCubemap::get_output_port_name(int p_port) const { return p_port == 0 ? "rgb" : "alpha"; } +bool VisualShaderNodeCubemap::is_output_port_expandable(int p_port) const { + if (p_port == 0) { + return true; + } + return false; +} + Vector<VisualShader::DefaultTextureParam> VisualShaderNodeCubemap::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { VisualShader::DefaultTextureParam dtp; dtp.name = make_unique_id(p_type, p_id, "cube"); @@ -1308,7 +1420,7 @@ void VisualShaderNodeCubemap::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "source", PROPERTY_HINT_ENUM, "Texture,SamplerPort"), "set_source", "get_source"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "cube_map", PROPERTY_HINT_RESOURCE_TYPE, "Cubemap"), "set_cube_map", "get_cube_map"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_type", PROPERTY_HINT_ENUM, "Data,Color,Normalmap"), "set_texture_type", "get_texture_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_type", PROPERTY_HINT_ENUM, "Data,Color,Normal Map"), "set_texture_type", "get_texture_type"); BIND_ENUM_CONSTANT(SOURCE_TEXTURE); BIND_ENUM_CONSTANT(SOURCE_PORT); @@ -1409,7 +1521,7 @@ void VisualShaderNodeFloatOp::_bind_methods() { ClassDB::bind_method(D_METHOD("set_operator", "op"), &VisualShaderNodeFloatOp::set_operator); ClassDB::bind_method(D_METHOD("get_operator"), &VisualShaderNodeFloatOp::get_operator); - ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "Add,Sub,Multiply,Divide,Remainder,Power,Max,Min,Atan2,Step"), "set_operator", "get_operator"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "Add,Subtract,Multiply,Divide,Remainder,Power,Max,Min,ATan2,Step"), "set_operator", "get_operator"); BIND_ENUM_CONSTANT(OP_ADD); BIND_ENUM_CONSTANT(OP_SUB); @@ -1506,7 +1618,7 @@ void VisualShaderNodeIntOp::_bind_methods() { ClassDB::bind_method(D_METHOD("set_operator", "op"), &VisualShaderNodeIntOp::set_operator); ClassDB::bind_method(D_METHOD("get_operator"), &VisualShaderNodeIntOp::get_operator); - ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "Add,Sub,Multiply,Divide,Remainder,Max,Min"), "set_operator", "get_operator"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "Add,Subtract,Multiply,Divide,Remainder,Max,Min"), "set_operator", "get_operator"); BIND_ENUM_CONSTANT(OP_ADD); BIND_ENUM_CONSTANT(OP_SUB); @@ -1615,7 +1727,7 @@ void VisualShaderNodeVectorOp::_bind_methods() { ClassDB::bind_method(D_METHOD("set_operator", "op"), &VisualShaderNodeVectorOp::set_operator); ClassDB::bind_method(D_METHOD("get_operator"), &VisualShaderNodeVectorOp::get_operator); - ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "Add,Sub,Multiply,Divide,Remainder,Power,Max,Min,Cross,Atan2,Reflect,Step"), "set_operator", "get_operator"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "Add,Subtract,Multiply,Divide,Remainder,Power,Max,Min,Cross,ATan2,Reflect,Step"), "set_operator", "get_operator"); BIND_ENUM_CONSTANT(OP_ADD); BIND_ENUM_CONSTANT(OP_SUB); @@ -1785,7 +1897,7 @@ void VisualShaderNodeColorOp::_bind_methods() { ClassDB::bind_method(D_METHOD("set_operator", "op"), &VisualShaderNodeColorOp::set_operator); ClassDB::bind_method(D_METHOD("get_operator"), &VisualShaderNodeColorOp::get_operator); - ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "Screen,Difference,Darken,Lighten,Overlay,Dodge,Burn,SoftLight,HardLight"), "set_operator", "get_operator"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "Screen,Difference,Darken,Lighten,Overlay,Dodge,Burn,Soft Light,Hard Light"), "set_operator", "get_operator"); BIND_ENUM_CONSTANT(OP_SCREEN); BIND_ENUM_CONSTANT(OP_DIFFERENCE); @@ -1873,8 +1985,8 @@ void VisualShaderNodeTransformMult::_bind_methods() { } VisualShaderNodeTransformMult::VisualShaderNodeTransformMult() { - set_input_port_default_value(0, Transform()); - set_input_port_default_value(1, Transform()); + set_input_port_default_value(0, Transform3D()); + set_input_port_default_value(1, Transform3D()); } ////////////// TransformVec Mult @@ -1947,7 +2059,7 @@ void VisualShaderNodeTransformVecMult::_bind_methods() { } VisualShaderNodeTransformVecMult::VisualShaderNodeTransformVecMult() { - set_input_port_default_value(0, Transform()); + set_input_port_default_value(0, Transform3D()); set_input_port_default_value(1, Vector3()); } @@ -2468,7 +2580,144 @@ void VisualShaderNodeTransformFunc::_bind_methods() { } VisualShaderNodeTransformFunc::VisualShaderNodeTransformFunc() { - set_input_port_default_value(0, Transform()); + set_input_port_default_value(0, Transform3D()); +} + +////////////// UV Func + +String VisualShaderNodeUVFunc::get_caption() const { + return "UVFunc"; +} + +int VisualShaderNodeUVFunc::get_input_port_count() const { + return 3; +} + +VisualShaderNodeUVFunc::PortType VisualShaderNodeUVFunc::get_input_port_type(int p_port) const { + switch (p_port) { + case 0: + [[fallthrough]]; // uv + case 1: + return PORT_TYPE_VECTOR; // scale + case 2: + return PORT_TYPE_VECTOR; // offset & pivot + default: + break; + } + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeUVFunc::get_input_port_name(int p_port) const { + switch (p_port) { + case 0: + return "uv"; + case 1: + return "scale"; + case 2: + switch (func) { + case FUNC_PANNING: + return "offset"; + case FUNC_SCALING: + return "pivot"; + case FUNC_MAX: + break; + default: + break; + } + break; + default: + break; + } + return ""; +} + +String VisualShaderNodeUVFunc::get_input_port_default_hint(int p_port) const { + if (p_port == 0) { + return "UV"; + } + return ""; +} + +int VisualShaderNodeUVFunc::get_output_port_count() const { + return 1; +} + +VisualShaderNodeUVFunc::PortType VisualShaderNodeUVFunc::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeUVFunc::get_output_port_name(int p_port) const { + return "uv"; +} + +bool VisualShaderNodeUVFunc::is_show_prop_names() const { + return true; +} + +String VisualShaderNodeUVFunc::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + String code; + + String uv; + if (p_input_vars[0].is_empty()) { + uv = "vec3(UV.xy, 0.0)"; + } else { + uv = vformat("%s", p_input_vars[0]); + } + String scale = vformat("%s", p_input_vars[1]); + String offset_pivot = vformat("%s", p_input_vars[2]); + + switch (func) { + case FUNC_PANNING: { + code += vformat("\t%s = fma(%s, %s, %s);\n", p_output_vars[0], offset_pivot, scale, uv); + } break; + case FUNC_SCALING: { + code += vformat("\t%s = fma((%s - %s), %s, %s);\n", p_output_vars[0], uv, offset_pivot, scale, offset_pivot); + } break; + case FUNC_MAX: + break; + } + return code; +} + +void VisualShaderNodeUVFunc::set_function(VisualShaderNodeUVFunc::Function p_func) { + ERR_FAIL_INDEX(int(p_func), FUNC_MAX); + if (func == p_func) { + return; + } + func = p_func; + + if (p_func == FUNC_PANNING) { + set_input_port_default_value(2, Vector3()); // offset + } else { // FUNC_SCALING + set_input_port_default_value(2, Vector3(0.5, 0.5, 0.0)); // pivot + } + emit_changed(); +} + +VisualShaderNodeUVFunc::Function VisualShaderNodeUVFunc::get_function() const { + return func; +} + +Vector<StringName> VisualShaderNodeUVFunc::get_editable_properties() const { + Vector<StringName> props; + props.push_back("function"); + return props; +} + +void VisualShaderNodeUVFunc::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_function", "func"), &VisualShaderNodeUVFunc::set_function); + ClassDB::bind_method(D_METHOD("get_function"), &VisualShaderNodeUVFunc::get_function); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Panning,Scaling"), "set_function", "get_function"); + + BIND_ENUM_CONSTANT(FUNC_PANNING); + BIND_ENUM_CONSTANT(FUNC_SCALING); + BIND_ENUM_CONSTANT(FUNC_MAX); +} + +VisualShaderNodeUVFunc::VisualShaderNodeUVFunc() { + set_input_port_default_value(1, Vector3(1.0, 1.0, 0.0)); // scale + set_input_port_default_value(2, Vector3()); // offset } ////////////// Dot Product @@ -2583,7 +2832,7 @@ String VisualShaderNodeDeterminant::generate_code(Shader::Mode p_mode, VisualSha } VisualShaderNodeDeterminant::VisualShaderNodeDeterminant() { - set_input_port_default_value(0, Transform()); + set_input_port_default_value(0, Transform3D()); } ////////////// Scalar Derivative Function @@ -3594,7 +3843,7 @@ String VisualShaderNodeTransformDecompose::generate_code(Shader::Mode p_mode, Vi } VisualShaderNodeTransformDecompose::VisualShaderNodeTransformDecompose() { - set_input_port_default_value(0, Transform()); + set_input_port_default_value(0, Transform3D()); } ////////////// Float Uniform @@ -3744,6 +3993,10 @@ bool VisualShaderNodeFloatUniform::is_qualifier_supported(Qualifier p_qual) cons return true; // all qualifiers are supported } +bool VisualShaderNodeFloatUniform::is_convertible_to_constant() const { + return true; // conversion is allowed +} + Vector<StringName> VisualShaderNodeFloatUniform::get_editable_properties() const { Vector<StringName> props = VisualShaderNodeUniform::get_editable_properties(); props.push_back("hint"); @@ -3895,7 +4148,7 @@ void VisualShaderNodeIntUniform::_bind_methods() { ClassDB::bind_method(D_METHOD("set_default_value", "value"), &VisualShaderNodeIntUniform::set_default_value); ClassDB::bind_method(D_METHOD("get_default_value"), &VisualShaderNodeIntUniform::get_default_value); - ADD_PROPERTY(PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_ENUM, "None,Range,Range+Step"), "set_hint", "get_hint"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_ENUM, "None,Range,Range + Step"), "set_hint", "get_hint"); ADD_PROPERTY(PropertyInfo(Variant::INT, "min"), "set_min", "get_min"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max"), "set_max", "get_max"); ADD_PROPERTY(PropertyInfo(Variant::INT, "step"), "set_step", "get_step"); @@ -3911,6 +4164,10 @@ bool VisualShaderNodeIntUniform::is_qualifier_supported(Qualifier p_qual) const return true; // all qualifiers are supported } +bool VisualShaderNodeIntUniform::is_convertible_to_constant() const { + return true; // conversion is allowed +} + Vector<StringName> VisualShaderNodeIntUniform::get_editable_properties() const { Vector<StringName> props = VisualShaderNodeUniform::get_editable_properties(); props.push_back("hint"); @@ -4019,6 +4276,10 @@ bool VisualShaderNodeBooleanUniform::is_qualifier_supported(Qualifier p_qual) co return true; // all qualifiers are supported } +bool VisualShaderNodeBooleanUniform::is_convertible_to_constant() const { + return true; // conversion is allowed +} + Vector<StringName> VisualShaderNodeBooleanUniform::get_editable_properties() const { Vector<StringName> props = VisualShaderNodeUniform::get_editable_properties(); props.push_back("default_value_enabled"); @@ -4113,6 +4374,10 @@ bool VisualShaderNodeColorUniform::is_qualifier_supported(Qualifier p_qual) cons return true; // all qualifiers are supported } +bool VisualShaderNodeColorUniform::is_convertible_to_constant() const { + return true; // conversion is allowed +} + Vector<StringName> VisualShaderNodeColorUniform::get_editable_properties() const { Vector<StringName> props = VisualShaderNodeUniform::get_editable_properties(); props.push_back("default_value_enabled"); @@ -4209,6 +4474,10 @@ bool VisualShaderNodeVec3Uniform::is_qualifier_supported(Qualifier p_qual) const return true; // all qualifiers are supported } +bool VisualShaderNodeVec3Uniform::is_convertible_to_constant() const { + return true; // conversion is allowed +} + Vector<StringName> VisualShaderNodeVec3Uniform::get_editable_properties() const { Vector<StringName> props = VisualShaderNodeUniform::get_editable_properties(); props.push_back("default_value_enabled"); @@ -4260,12 +4529,12 @@ bool VisualShaderNodeTransformUniform::is_default_value_enabled() const { return default_value_enabled; } -void VisualShaderNodeTransformUniform::set_default_value(const Transform &p_value) { +void VisualShaderNodeTransformUniform::set_default_value(const Transform3D &p_value) { default_value = p_value; emit_changed(); } -Transform VisualShaderNodeTransformUniform::get_default_value() const { +Transform3D VisualShaderNodeTransformUniform::get_default_value() const { return default_value; } @@ -4294,7 +4563,7 @@ void VisualShaderNodeTransformUniform::_bind_methods() { ClassDB::bind_method(D_METHOD("get_default_value"), &VisualShaderNodeTransformUniform::get_default_value); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "default_value_enabled"), "set_default_value_enabled", "is_default_value_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM, "default_value"), "set_default_value", "get_default_value"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "default_value"), "set_default_value", "get_default_value"); } bool VisualShaderNodeTransformUniform::is_show_prop_names() const { @@ -4309,6 +4578,10 @@ bool VisualShaderNodeTransformUniform::is_qualifier_supported(Qualifier p_qual) return true; // all qualifiers are supported } +bool VisualShaderNodeTransformUniform::is_convertible_to_constant() const { + return true; // conversion is allowed +} + Vector<StringName> VisualShaderNodeTransformUniform::get_editable_properties() const { Vector<StringName> props = VisualShaderNodeUniform::get_editable_properties(); props.push_back("default_value_enabled"); @@ -4463,7 +4736,7 @@ void VisualShaderNodeTextureUniform::_bind_methods() { ClassDB::bind_method(D_METHOD("set_color_default", "type"), &VisualShaderNodeTextureUniform::set_color_default); ClassDB::bind_method(D_METHOD("get_color_default"), &VisualShaderNodeTextureUniform::get_color_default); - ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_type", PROPERTY_HINT_ENUM, "Data,Color,Normalmap,Aniso"), "set_texture_type", "get_texture_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_type", PROPERTY_HINT_ENUM, "Data,Color,Normal Map,Anisotropic"), "set_texture_type", "get_texture_type"); ADD_PROPERTY(PropertyInfo(Variant::INT, "color_default", PROPERTY_HINT_ENUM, "White Default,Black Default"), "set_color_default", "get_color_default"); BIND_ENUM_CONSTANT(TYPE_DATA); @@ -4494,6 +4767,10 @@ bool VisualShaderNodeTextureUniform::is_qualifier_supported(Qualifier p_qual) co return false; } +bool VisualShaderNodeTextureUniform::is_convertible_to_constant() const { + return false; // conversion is not allowed +} + VisualShaderNodeTextureUniform::VisualShaderNodeTextureUniform() { simple_decl = false; } @@ -4632,16 +4909,18 @@ String VisualShaderNodeTexture2DArrayUniform::generate_global(Shader::Mode p_mod switch (texture_type) { case TYPE_DATA: - if (color_default == COLOR_DEFAULT_BLACK) + if (color_default == COLOR_DEFAULT_BLACK) { code += " : hint_black;\n"; - else + } else { code += ";\n"; + } break; case TYPE_COLOR: - if (color_default == COLOR_DEFAULT_BLACK) + if (color_default == COLOR_DEFAULT_BLACK) { code += " : hint_black_albedo;\n"; - else + } else { code += " : hint_albedo;\n"; + } break; case TYPE_NORMAL_MAP: code += " : hint_normal;\n"; @@ -4700,16 +4979,18 @@ String VisualShaderNodeTexture3DUniform::generate_global(Shader::Mode p_mode, Vi switch (texture_type) { case TYPE_DATA: - if (color_default == COLOR_DEFAULT_BLACK) + if (color_default == COLOR_DEFAULT_BLACK) { code += " : hint_black;\n"; - else + } else { code += ";\n"; + } break; case TYPE_COLOR: - if (color_default == COLOR_DEFAULT_BLACK) + if (color_default == COLOR_DEFAULT_BLACK) { code += " : hint_black_albedo;\n"; - else + } else { code += " : hint_albedo;\n"; + } break; case TYPE_NORMAL_MAP: code += " : hint_normal;\n"; @@ -4965,8 +5246,8 @@ void VisualShaderNodeSwitch::set_op_type(OpType p_op_type) { set_input_port_default_value(2, false); break; case OP_TYPE_TRANSFORM: - set_input_port_default_value(1, Transform()); - set_input_port_default_value(2, Transform()); + set_input_port_default_value(1, Transform3D()); + set_input_port_default_value(2, Transform3D()); break; default: break; @@ -5345,8 +5626,8 @@ void VisualShaderNodeCompare::set_comparison_type(ComparisonType p_type) { simple_decl = true; break; case CTYPE_TRANSFORM: - set_input_port_default_value(0, Transform()); - set_input_port_default_value(1, Transform()); + set_input_port_default_value(0, Transform3D()); + set_input_port_default_value(1, Transform3D()); simple_decl = true; break; } @@ -5519,3 +5800,127 @@ VisualShaderNodeMultiplyAdd::VisualShaderNodeMultiplyAdd() { set_input_port_default_value(1, 0.0); set_input_port_default_value(2, 0.0); } + +////////////// Billboard + +String VisualShaderNodeBillboard::get_caption() const { + return "GetBillboardMatrix"; +} + +int VisualShaderNodeBillboard::get_input_port_count() const { + return 0; +} + +VisualShaderNodeBillboard::PortType VisualShaderNodeBillboard::get_input_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeBillboard::get_input_port_name(int p_port) const { + return ""; +} + +int VisualShaderNodeBillboard::get_output_port_count() const { + return 1; +} + +VisualShaderNodeBillboard::PortType VisualShaderNodeBillboard::get_output_port_type(int p_port) const { + return PORT_TYPE_TRANSFORM; +} + +String VisualShaderNodeBillboard::get_output_port_name(int p_port) const { + return "model_view_matrix"; +} + +String VisualShaderNodeBillboard::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + String code; + + switch (billboard_type) { + case BILLBOARD_TYPE_ENABLED: + code += "\t{\n"; + code += "\t\tmat4 __mvm = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0], CAMERA_MATRIX[1], CAMERA_MATRIX[2], WORLD_MATRIX[3]);\n"; + if (keep_scale) { + code += "\t\t__mvm = __mvm * mat4(vec4(length(WORLD_MATRIX[0].xyz), 0.0, 0.0, 0.0), vec4(0.0, length(WORLD_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, length(WORLD_MATRIX[2].xyz), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; + } + code += "\t\t" + p_output_vars[0] + " = __mvm;\n"; + code += "\t}\n"; + break; + case BILLBOARD_TYPE_FIXED_Y: + code += "\t{\n"; + code += "\t\tmat4 __mvm = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0], WORLD_MATRIX[1], vec4(normalize(cross(CAMERA_MATRIX[0].xyz, WORLD_MATRIX[1].xyz)), 0.0), WORLD_MATRIX[3]);\n"; + if (keep_scale) { + code += "\t\t__mvm = __mvm * mat4(vec4(length(WORLD_MATRIX[0].xyz), 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, length(WORLD_MATRIX[2].xyz), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; + } else { + code += "\t\t__mvm = __mvm * mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0 / length(WORLD_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; + } + code += "\t\t" + p_output_vars[0] + " = __mvm;\n"; + code += "\t}\n"; + break; + case BILLBOARD_TYPE_PARTICLES: + code += "\t{\n"; + code += "\t\tmat4 __wm = mat4(normalize(CAMERA_MATRIX[0]) * length(WORLD_MATRIX[0]), normalize(CAMERA_MATRIX[1]) * length(WORLD_MATRIX[0]), normalize(CAMERA_MATRIX[2]) * length(WORLD_MATRIX[2]), WORLD_MATRIX[3]);\n"; + code += "\t\t__wm = __wm * mat4(vec4(cos(INSTANCE_CUSTOM.x), -sin(INSTANCE_CUSTOM.x), 0.0, 0.0), vec4(sin(INSTANCE_CUSTOM.x), cos(INSTANCE_CUSTOM.x), 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; + code += "\t\t" + p_output_vars[0] + " = INV_CAMERA_MATRIX * __wm;\n"; + code += "\t}\n"; + break; + default: + code += "\t" + p_output_vars[0] + " = mat4(1.0);\n"; + break; + } + + return code; +} + +bool VisualShaderNodeBillboard::is_show_prop_names() const { + return true; +} + +void VisualShaderNodeBillboard::set_billboard_type(BillboardType p_billboard_type) { + ERR_FAIL_INDEX((int)p_billboard_type, BILLBOARD_TYPE_MAX); + billboard_type = p_billboard_type; + simple_decl = bool(billboard_type == BILLBOARD_TYPE_DISABLED); + set_disabled(simple_decl); + emit_changed(); +} + +VisualShaderNodeBillboard::BillboardType VisualShaderNodeBillboard::get_billboard_type() const { + return billboard_type; +} + +void VisualShaderNodeBillboard::set_keep_scale_enabled(bool p_enabled) { + keep_scale = p_enabled; + emit_changed(); +} + +bool VisualShaderNodeBillboard::is_keep_scale_enabled() const { + return keep_scale; +} + +Vector<StringName> VisualShaderNodeBillboard::get_editable_properties() const { + Vector<StringName> props; + props.push_back("billboard_type"); + if (billboard_type == BILLBOARD_TYPE_ENABLED || billboard_type == BILLBOARD_TYPE_FIXED_Y) { + props.push_back("keep_scale"); + } + return props; +} + +void VisualShaderNodeBillboard::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_billboard_type", "billboard_type"), &VisualShaderNodeBillboard::set_billboard_type); + ClassDB::bind_method(D_METHOD("get_billboard_type"), &VisualShaderNodeBillboard::get_billboard_type); + + ClassDB::bind_method(D_METHOD("set_keep_scale_enabled", "enabled"), &VisualShaderNodeBillboard::set_keep_scale_enabled); + ClassDB::bind_method(D_METHOD("is_keep_scale_enabled"), &VisualShaderNodeBillboard::is_keep_scale_enabled); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "billboard_type", PROPERTY_HINT_ENUM, "Disabled,Enabled,Y-Billboard,Particles"), "set_billboard_type", "get_billboard_type"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_scale"), "set_keep_scale_enabled", "is_keep_scale_enabled"); + + BIND_ENUM_CONSTANT(BILLBOARD_TYPE_DISABLED); + BIND_ENUM_CONSTANT(BILLBOARD_TYPE_ENABLED); + BIND_ENUM_CONSTANT(BILLBOARD_TYPE_FIXED_Y); + BIND_ENUM_CONSTANT(BILLBOARD_TYPE_PARTICLES); + BIND_ENUM_CONSTANT(BILLBOARD_TYPE_MAX); +} + +VisualShaderNodeBillboard::VisualShaderNodeBillboard() { + simple_decl = false; +} diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h index a5d0fe4649..33a45a4384 100644 --- a/scene/resources/visual_shader_nodes.h +++ b/scene/resources/visual_shader_nodes.h @@ -74,7 +74,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_constant(float p_value); float get_constant() const; @@ -104,7 +104,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_constant(int p_value); int get_constant() const; @@ -134,7 +134,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_constant(bool p_value); bool get_constant() const; @@ -163,8 +163,9 @@ public: virtual int get_output_port_count() const override; virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; + virtual bool is_output_port_expandable(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_constant(Color p_value); Color get_constant() const; @@ -194,7 +195,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_constant(Vector3 p_value); Vector3 get_constant() const; @@ -208,7 +209,7 @@ public: class VisualShaderNodeTransformConstant : public VisualShaderNodeConstant { GDCLASS(VisualShaderNodeTransformConstant, VisualShaderNodeConstant); - Transform constant; + Transform3D constant; protected: static void _bind_methods(); @@ -224,10 +225,10 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; - void set_constant(Transform p_value); - Transform get_constant() const; + void set_constant(Transform3D p_value); + Transform3D get_constant() const; virtual Vector<StringName> get_editable_properties() const override; @@ -275,12 +276,13 @@ public: virtual int get_output_port_count() const override; virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; + virtual bool is_output_port_expandable(int p_port) const override; virtual String get_input_port_default_hint(int p_port) const override; virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const override; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_source(Source p_source); Source get_source() const; @@ -323,7 +325,7 @@ public: virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const override; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_texture(Ref<CurveTexture> p_value); Ref<CurveTexture> get_texture() const; @@ -336,6 +338,39 @@ public: /////////////////////////////////////// +class VisualShaderNodeCurveXYZTexture : public VisualShaderNodeResizableBase { + GDCLASS(VisualShaderNodeCurveXYZTexture, VisualShaderNodeResizableBase); + Ref<CurveXYZTexture> texture; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + + virtual int get_output_port_count() const override; + virtual PortType get_output_port_type(int p_port) const override; + virtual String get_output_port_name(int p_port) const override; + + virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const override; + virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + void set_texture(Ref<CurveXYZTexture> p_value); + Ref<CurveXYZTexture> get_texture() const; + + virtual Vector<StringName> get_editable_properties() const override; + virtual bool is_use_prop_slots() const override; + + VisualShaderNodeCurveXYZTexture(); +}; + +/////////////////////////////////////// + class VisualShaderNodeSample3D : public VisualShaderNode { GDCLASS(VisualShaderNodeSample3D, VisualShaderNode); @@ -359,8 +394,9 @@ public: virtual int get_output_port_count() const override; virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; + virtual bool is_output_port_expandable(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_source(Source p_source); Source get_source() const; @@ -452,10 +488,11 @@ public: virtual int get_output_port_count() const override; virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; + virtual bool is_output_port_expandable(int p_port) const override; virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const override; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_source(Source p_source); Source get_source() const; @@ -512,7 +549,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_operator(Operator p_op); Operator get_operator() const; @@ -554,7 +591,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_operator(Operator p_op); Operator get_operator() const; @@ -601,7 +638,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_operator(Operator p_op); Operator get_operator() const; @@ -647,7 +684,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_operator(Operator p_op); Operator get_operator() const; @@ -690,7 +727,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_operator(Operator p_op); Operator get_operator() const; @@ -733,7 +770,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_operator(Operator p_op); Operator get_operator() const; @@ -804,7 +841,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_function(Function p_func); Function get_function() const; @@ -846,7 +883,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_function(Function p_func); Function get_function() const; @@ -920,7 +957,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_function(Function p_func); Function get_function() const; @@ -961,7 +998,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_function(Function p_func); Function get_function() const; @@ -1002,7 +1039,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_function(Function p_func); Function get_function() const; @@ -1015,6 +1052,51 @@ public: VARIANT_ENUM_CAST(VisualShaderNodeTransformFunc::Function) /////////////////////////////////////// +/// UV FUNC +/////////////////////////////////////// + +class VisualShaderNodeUVFunc : public VisualShaderNode { + GDCLASS(VisualShaderNodeUVFunc, VisualShaderNode); + +public: + enum Function { + FUNC_PANNING, + FUNC_SCALING, + FUNC_MAX, + }; + +protected: + Function func = FUNC_PANNING; + + static void _bind_methods(); + +public: + virtual String get_caption() const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + virtual String get_input_port_default_hint(int p_port) const override; + + virtual int get_output_port_count() const override; + virtual PortType get_output_port_type(int p_port) const override; + virtual String get_output_port_name(int p_port) const override; + + virtual bool is_show_prop_names() const override; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + void set_function(Function p_op); + Function get_function() const; + + virtual Vector<StringName> get_editable_properties() const override; + + VisualShaderNodeUVFunc(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeUVFunc::Function) + +/////////////////////////////////////// /// DOT /////////////////////////////////////// @@ -1032,7 +1114,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeDotProduct(); }; @@ -1055,7 +1137,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeVectorLen(); }; @@ -1078,7 +1160,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeDeterminant(); }; @@ -1118,7 +1200,7 @@ public: virtual Vector<StringName> get_editable_properties() const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeClamp(); }; @@ -1155,7 +1237,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_function(Function p_func); Function get_function() const; @@ -1195,7 +1277,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_function(Function p_func); Function get_function() const; @@ -1225,7 +1307,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeFaceForward(); }; @@ -1248,7 +1330,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeOuterProduct(); }; @@ -1288,7 +1370,7 @@ public: virtual Vector<StringName> get_editable_properties() const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeStep(); }; @@ -1330,7 +1412,7 @@ public: virtual Vector<StringName> get_editable_properties() const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeSmoothStep(); }; @@ -1355,7 +1437,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeVectorDistance(); }; @@ -1378,7 +1460,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeVectorRefract(); }; @@ -1418,7 +1500,7 @@ public: virtual Vector<StringName> get_editable_properties() const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeMix(); }; @@ -1443,7 +1525,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeVectorCompose(); }; @@ -1464,7 +1546,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeTransformCompose(); }; @@ -1487,7 +1569,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeVectorDecompose(); }; @@ -1508,7 +1590,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeTransformDecompose(); }; @@ -1550,7 +1632,7 @@ public: virtual String get_output_port_name(int p_port) const override; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; virtual bool is_show_prop_names() const override; virtual bool is_use_prop_slots() const override; @@ -1574,6 +1656,7 @@ public: float get_default_value() const; bool is_qualifier_supported(Qualifier p_qual) const override; + bool is_convertible_to_constant() const override; virtual Vector<StringName> get_editable_properties() const override; @@ -1615,7 +1698,7 @@ public: virtual String get_output_port_name(int p_port) const override; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; virtual bool is_show_prop_names() const override; virtual bool is_use_prop_slots() const override; @@ -1639,6 +1722,7 @@ public: int get_default_value() const; bool is_qualifier_supported(Qualifier p_qual) const override; + bool is_convertible_to_constant() const override; virtual Vector<StringName> get_editable_properties() const override; @@ -1671,7 +1755,7 @@ public: virtual String get_output_port_name(int p_port) const override; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; virtual bool is_show_prop_names() const override; virtual bool is_use_prop_slots() const override; @@ -1683,6 +1767,7 @@ public: bool get_default_value() const; bool is_qualifier_supported(Qualifier p_qual) const override; + bool is_convertible_to_constant() const override; virtual Vector<StringName> get_editable_properties() const override; @@ -1713,7 +1798,7 @@ public: virtual String get_output_port_name(int p_port) const override; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; virtual bool is_show_prop_names() const override; @@ -1724,6 +1809,7 @@ public: Color get_default_value() const; bool is_qualifier_supported(Qualifier p_qual) const override; + bool is_convertible_to_constant() const override; virtual Vector<StringName> get_editable_properties() const override; @@ -1754,7 +1840,7 @@ public: virtual String get_output_port_name(int p_port) const override; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; virtual bool is_show_prop_names() const override; virtual bool is_use_prop_slots() const override; @@ -1766,6 +1852,7 @@ public: Vector3 get_default_value() const; bool is_qualifier_supported(Qualifier p_qual) const override; + bool is_convertible_to_constant() const override; virtual Vector<StringName> get_editable_properties() const override; @@ -1779,7 +1866,7 @@ class VisualShaderNodeTransformUniform : public VisualShaderNodeUniform { private: bool default_value_enabled = false; - Transform default_value = Transform(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0); + Transform3D default_value = Transform3D(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0); protected: static void _bind_methods(); @@ -1796,7 +1883,7 @@ public: virtual String get_output_port_name(int p_port) const override; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; virtual bool is_show_prop_names() const override; virtual bool is_use_prop_slots() const override; @@ -1804,10 +1891,11 @@ public: void set_default_value_enabled(bool p_enabled); bool is_default_value_enabled() const; - void set_default_value(const Transform &p_value); - Transform get_default_value() const; + void set_default_value(const Transform3D &p_value); + Transform3D get_default_value() const; bool is_qualifier_supported(Qualifier p_qual) const override; + bool is_convertible_to_constant() const override; virtual Vector<StringName> get_editable_properties() const override; @@ -1852,7 +1940,7 @@ public: virtual String get_output_port_name(int p_port) const override; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; virtual bool is_code_generated() const override; @@ -1865,6 +1953,7 @@ public: ColorDefault get_color_default() const; bool is_qualifier_supported(Qualifier p_qual) const override; + bool is_convertible_to_constant() const override; VisualShaderNodeTextureUniform(); }; @@ -1888,7 +1977,7 @@ public: virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; virtual String generate_global_per_func(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeTextureUniformTriplanar(); }; @@ -1911,7 +2000,7 @@ public: virtual String get_input_port_default_hint(int p_port) const override; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeTexture2DArrayUniform(); }; @@ -1934,7 +2023,7 @@ public: virtual String get_input_port_default_hint(int p_port) const override; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeTexture3DUniform(); }; @@ -1957,7 +2046,7 @@ public: virtual String get_input_port_default_hint(int p_port) const override; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; VisualShaderNodeCubemapUniform(); }; @@ -2085,7 +2174,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_function(Function p_func); Function get_function() const; @@ -2146,7 +2235,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_comparison_type(ComparisonType p_type); ComparisonType get_comparison_type() const; @@ -2194,7 +2283,7 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; - virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; void set_op_type(OpType p_type); OpType get_op_type() const; @@ -2206,4 +2295,51 @@ public: VARIANT_ENUM_CAST(VisualShaderNodeMultiplyAdd::OpType) +class VisualShaderNodeBillboard : public VisualShaderNode { + GDCLASS(VisualShaderNodeBillboard, VisualShaderNode); + +public: + enum BillboardType { + BILLBOARD_TYPE_DISABLED, + BILLBOARD_TYPE_ENABLED, + BILLBOARD_TYPE_FIXED_Y, + BILLBOARD_TYPE_PARTICLES, + BILLBOARD_TYPE_MAX, + }; + +protected: + BillboardType billboard_type = BILLBOARD_TYPE_ENABLED; + bool keep_scale = false; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + + virtual int get_output_port_count() const override; + virtual PortType get_output_port_type(int p_port) const override; + virtual String get_output_port_name(int p_port) const override; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + virtual bool is_show_prop_names() const override; + + void set_billboard_type(BillboardType p_billboard_type); + BillboardType get_billboard_type() const; + + void set_keep_scale_enabled(bool p_enabled); + bool is_keep_scale_enabled() const; + + virtual Vector<StringName> get_editable_properties() const override; + + VisualShaderNodeBillboard(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeBillboard::BillboardType) + #endif // VISUAL_SHADER_NODES_H diff --git a/scene/resources/visual_shader_particle_nodes.cpp b/scene/resources/visual_shader_particle_nodes.cpp new file mode 100644 index 0000000000..29d583a82a --- /dev/null +++ b/scene/resources/visual_shader_particle_nodes.cpp @@ -0,0 +1,1025 @@ +/*************************************************************************/ +/* visual_shader_particle_nodes.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "visual_shader_particle_nodes.h" + +// VisualShaderNodeParticleEmitter + +int VisualShaderNodeParticleEmitter::get_output_port_count() const { + return 1; +} + +VisualShaderNodeParticleEmitter::PortType VisualShaderNodeParticleEmitter::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeParticleEmitter::get_output_port_name(int p_port) const { + if (p_port == 0) { + return "position"; + } + return String(); +} + +VisualShaderNodeParticleEmitter::VisualShaderNodeParticleEmitter() { +} + +// VisualShaderNodeParticleSphereEmitter + +String VisualShaderNodeParticleSphereEmitter::get_caption() const { + return "SphereEmitter"; +} + +int VisualShaderNodeParticleSphereEmitter::get_input_port_count() const { + return 2; +} + +VisualShaderNodeParticleSphereEmitter::PortType VisualShaderNodeParticleSphereEmitter::get_input_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeParticleSphereEmitter::get_input_port_name(int p_port) const { + if (p_port == 0) { + return "radius"; + } else if (p_port == 1) { + return "inner_radius"; + } + return String(); +} + +String VisualShaderNodeParticleSphereEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + String code; + code += "vec3 __get_random_point_in_sphere(inout uint seed, float radius, float inner_radius) {\n"; + code += "\treturn __get_random_unit_vec3(seed) * __randf_range(seed, inner_radius, radius);\n"; + code += "}\n\n"; + return code; +} + +String VisualShaderNodeParticleSphereEmitter::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + String code; + code += "\t" + p_output_vars[0] + " = __get_random_point_in_sphere(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ");\n"; + return code; +} + +VisualShaderNodeParticleSphereEmitter::VisualShaderNodeParticleSphereEmitter() { + set_input_port_default_value(0, 10.0); + set_input_port_default_value(1, 0.0); +} + +// VisualShaderNodeParticleBoxEmitter + +String VisualShaderNodeParticleBoxEmitter::get_caption() const { + return "BoxEmitter"; +} + +int VisualShaderNodeParticleBoxEmitter::get_input_port_count() const { + return 1; +} + +VisualShaderNodeParticleBoxEmitter::PortType VisualShaderNodeParticleBoxEmitter::get_input_port_type(int p_port) const { + if (p_port == 0) { + return PORT_TYPE_VECTOR; + } + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeParticleBoxEmitter::get_input_port_name(int p_port) const { + if (p_port == 0) { + return "extents"; + } + return String(); +} + +String VisualShaderNodeParticleBoxEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + String code; + code += "vec3 __get_random_point_in_box(inout uint seed, vec3 extents) {\n"; + code += "\tvec3 half_extents = extents / 2.0;\n"; + code += "\treturn vec3(__randf_range(seed, -half_extents.x, half_extents.x), __randf_range(seed, -half_extents.y, half_extents.y), __randf_range(seed, -half_extents.z, half_extents.z));\n"; + code += "}\n\n"; + return code; +} + +String VisualShaderNodeParticleBoxEmitter::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + String code; + code += "\t" + p_output_vars[0] + " = __get_random_point_in_box(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ");\n"; + return code; +} + +VisualShaderNodeParticleBoxEmitter::VisualShaderNodeParticleBoxEmitter() { + set_input_port_default_value(0, Vector3(1.0, 1.0, 1.0)); +} + +// VisualShaderNodeParticleRingEmitter + +String VisualShaderNodeParticleRingEmitter::get_caption() const { + return "RingEmitter"; +} + +int VisualShaderNodeParticleRingEmitter::get_input_port_count() const { + return 3; +} + +VisualShaderNodeParticleRingEmitter::PortType VisualShaderNodeParticleRingEmitter::get_input_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeParticleRingEmitter::get_input_port_name(int p_port) const { + if (p_port == 0) { + return "radius"; + } else if (p_port == 1) { + return "inner_radius"; + } else if (p_port == 2) { + return "height"; + } + return String(); +} + +String VisualShaderNodeParticleRingEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + String code; + code += "vec3 __get_random_point_on_ring(inout uint seed, float radius, float inner_radius, float height) {\n"; + code += "\tfloat angle = __rand_from_seed(seed) * PI * 2.0;\n"; + code += "\tvec2 ring = vec2(sin(angle), cos(angle)) * __randf_range(seed, inner_radius, radius);\n"; + code += "\treturn vec3(ring.x, __randf_range(seed, min(0.0, height), max(0.0, height)), ring.y);\n"; + code += "}\n\n"; + return code; +} + +String VisualShaderNodeParticleRingEmitter::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + String code; + code = "\t" + p_output_vars[0] + " = __get_random_point_on_ring(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ", " + (p_input_vars[2].is_empty() ? (String)get_input_port_default_value(2) : p_input_vars[2]) + ");\n"; + return code; +} + +VisualShaderNodeParticleRingEmitter::VisualShaderNodeParticleRingEmitter() { + set_input_port_default_value(0, 10.0); + set_input_port_default_value(1, 0.0); + set_input_port_default_value(2, 0.0); +} + +// VisualShaderNodeParticleMultiplyByAxisAngle + +void VisualShaderNodeParticleMultiplyByAxisAngle::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_degrees_mode", "enabled"), &VisualShaderNodeParticleMultiplyByAxisAngle::set_degrees_mode); + ClassDB::bind_method(D_METHOD("is_degrees_mode"), &VisualShaderNodeParticleMultiplyByAxisAngle::is_degrees_mode); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "degrees_mode"), "set_degrees_mode", "is_degrees_mode"); +} + +String VisualShaderNodeParticleMultiplyByAxisAngle::get_caption() const { + return "MultiplyByAxisAngle"; +} + +int VisualShaderNodeParticleMultiplyByAxisAngle::get_input_port_count() const { + return 3; +} + +VisualShaderNodeParticleMultiplyByAxisAngle::PortType VisualShaderNodeParticleMultiplyByAxisAngle::get_input_port_type(int p_port) const { + if (p_port == 0 || p_port == 1) { // position, rotation_axis + return PORT_TYPE_VECTOR; + } + return PORT_TYPE_SCALAR; // angle (degrees/radians) +} + +String VisualShaderNodeParticleMultiplyByAxisAngle::get_input_port_name(int p_port) const { + if (p_port == 0) { + return "position"; + } + if (p_port == 1) { + return "axis"; + } + if (p_port == 2) { + if (degrees_mode) { + return "angle (degrees)"; + } else { + return "angle (radians)"; + } + } + return String(); +} + +bool VisualShaderNodeParticleMultiplyByAxisAngle::is_show_prop_names() const { + return true; +} + +int VisualShaderNodeParticleMultiplyByAxisAngle::get_output_port_count() const { + return 1; +} + +VisualShaderNodeParticleMultiplyByAxisAngle::PortType VisualShaderNodeParticleMultiplyByAxisAngle::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeParticleMultiplyByAxisAngle::get_output_port_name(int p_port) const { + return "position"; +} + +String VisualShaderNodeParticleMultiplyByAxisAngle::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + String code; + if (degrees_mode) { + code += "\t" + p_output_vars[0] + " = __build_rotation_mat3(" + (p_input_vars[1].is_empty() ? ("vec3" + (String)get_input_port_default_value(1)) : p_input_vars[1]) + ", radians(" + (p_input_vars[2].is_empty() ? (String)get_input_port_default_value(2) : p_input_vars[2]) + ")) * " + (p_input_vars[0].is_empty() ? "vec3(0.0)" : p_input_vars[0]) + ";\n"; + } else { + code += "\t" + p_output_vars[0] + " = __build_rotation_mat3(" + (p_input_vars[1].is_empty() ? ("vec3" + (String)get_input_port_default_value(1)) : p_input_vars[1]) + ", " + (p_input_vars[2].is_empty() ? (String)get_input_port_default_value(2) : p_input_vars[2]) + ") * " + (p_input_vars[0].is_empty() ? "vec3(0.0)" : p_input_vars[0]) + ";\n"; + } + return code; +} + +void VisualShaderNodeParticleMultiplyByAxisAngle::set_degrees_mode(bool p_enabled) { + degrees_mode = p_enabled; + emit_changed(); +} + +bool VisualShaderNodeParticleMultiplyByAxisAngle::is_degrees_mode() const { + return degrees_mode; +} + +Vector<StringName> VisualShaderNodeParticleMultiplyByAxisAngle::get_editable_properties() const { + Vector<StringName> props; + props.push_back("degrees_mode"); + props.push_back("axis_amount"); + return props; +} + +VisualShaderNodeParticleMultiplyByAxisAngle::VisualShaderNodeParticleMultiplyByAxisAngle() { + set_input_port_default_value(1, Vector3(1, 0, 0)); + set_input_port_default_value(2, 0.0); +} + +// VisualShaderNodeParticleConeVelocity + +String VisualShaderNodeParticleConeVelocity::get_caption() const { + return "ConeVelocity"; +} + +int VisualShaderNodeParticleConeVelocity::get_input_port_count() const { + return 2; +} + +VisualShaderNodeParticleConeVelocity::PortType VisualShaderNodeParticleConeVelocity::get_input_port_type(int p_port) const { + if (p_port == 0) { + return PORT_TYPE_VECTOR; + } else if (p_port == 1) { + return PORT_TYPE_SCALAR; + } + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeParticleConeVelocity::get_input_port_name(int p_port) const { + if (p_port == 0) { + return "direction"; + } else if (p_port == 1) { + return "spread(degrees)"; + } + return String(); +} + +int VisualShaderNodeParticleConeVelocity::get_output_port_count() const { + return 1; +} + +VisualShaderNodeParticleConeVelocity::PortType VisualShaderNodeParticleConeVelocity::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeParticleConeVelocity::get_output_port_name(int p_port) const { + if (p_port == 0) { + return "velocity"; + } + return String(); +} + +String VisualShaderNodeParticleConeVelocity::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + String code; + code += "\t__radians = radians(" + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ");\n"; + code += "\t__scalar_buff1 = __rand_from_seed_m1_p1(__seed) * __radians;\n"; + code += "\t__scalar_buff2 = __rand_from_seed_m1_p1(__seed) * __radians;\n"; + code += "\t__vec3_buff1 = " + (p_input_vars[0].is_empty() ? "vec3" + (String)get_input_port_default_value(0) : p_input_vars[0]) + ";\n"; + code += "\t__scalar_buff1 += __vec3_buff1.z != 0.0 ? atan(__vec3_buff1.x, __vec3_buff1.z) : sign(__vec3_buff1.x) * (PI / 2.0);\n"; + code += "\t__scalar_buff2 += __vec3_buff1.z != 0.0 ? atan(__vec3_buff1.y, abs(__vec3_buff1.z)) : (__vec3_buff1.x != 0.0 ? atan(__vec3_buff1.y, abs(__vec3_buff1.x)) : sign(__vec3_buff1.y) * (PI / 2.0));\n"; + code += "\t__vec3_buff1 = vec3(sin(__scalar_buff1), 0.0, cos(__scalar_buff1));\n"; + code += "\t__vec3_buff2 = vec3(0.0, sin(__scalar_buff2), cos(__scalar_buff2));\n"; + code += "\t__vec3_buff2.z = __vec3_buff2.z / max(0.0001, sqrt(abs(__vec3_buff2.z)));\n"; + code += "\t" + p_output_vars[0] + " = normalize(vec3(__vec3_buff1.x * __vec3_buff2.z, __vec3_buff2.y, __vec3_buff1.z * __vec3_buff2.z));\n"; + return code; +} + +VisualShaderNodeParticleConeVelocity::VisualShaderNodeParticleConeVelocity() { + set_input_port_default_value(0, Vector3(1, 0, 0)); + set_input_port_default_value(1, 45.0); +} + +// VisualShaderNodeParticleRandomness + +void VisualShaderNodeParticleRandomness::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_op_type", "type"), &VisualShaderNodeParticleRandomness::set_op_type); + ClassDB::bind_method(D_METHOD("get_op_type"), &VisualShaderNodeParticleRandomness::get_op_type); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Scalar,Vector"), "set_op_type", "get_op_type"); + + BIND_ENUM_CONSTANT(OP_TYPE_SCALAR); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR); + BIND_ENUM_CONSTANT(OP_TYPE_MAX); +} + +Vector<StringName> VisualShaderNodeParticleRandomness::get_editable_properties() const { + Vector<StringName> props; + props.push_back("op_type"); + return props; +} + +String VisualShaderNodeParticleRandomness::get_caption() const { + return "ParticleRandomness"; +} + +int VisualShaderNodeParticleRandomness::get_output_port_count() const { + return 1; +} + +VisualShaderNodeParticleRandomness::PortType VisualShaderNodeParticleRandomness::get_output_port_type(int p_port) const { + if (op_type == OP_TYPE_VECTOR) { + return PORT_TYPE_VECTOR; + } + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeParticleRandomness::get_output_port_name(int p_port) const { + return "random"; +} + +int VisualShaderNodeParticleRandomness::get_input_port_count() const { + return 2; +} + +VisualShaderNodeParticleRandomness::PortType VisualShaderNodeParticleRandomness::get_input_port_type(int p_port) const { + if (op_type == OP_TYPE_VECTOR) { + return PORT_TYPE_VECTOR; + } + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeParticleRandomness::get_input_port_name(int p_port) const { + if (p_port == 0) { + return "min"; + } else if (p_port == 1) { + return "max"; + } + return String(); +} + +String VisualShaderNodeParticleRandomness::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + String code; + if (op_type == OP_TYPE_SCALAR) { + code += vformat("\t%s = __randf_range(__seed, %s, %s);\n", p_output_vars[0], p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0], p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]); + } else if (op_type == OP_TYPE_VECTOR) { + code += vformat("\t%s = __randv_range(__seed, %s, %s);\n", p_output_vars[0], p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0], p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]); + } + return code; +} + +void VisualShaderNodeParticleRandomness::set_op_type(OpType p_op_type) { + ERR_FAIL_INDEX((int)p_op_type, OP_TYPE_MAX); + if (p_op_type != op_type) { + if (p_op_type == OP_TYPE_SCALAR) { + set_input_port_default_value(0, 0.0); + set_input_port_default_value(1, 1.0); + } else { + set_input_port_default_value(0, Vector3(-1.0, -1.0, -1.0)); + set_input_port_default_value(1, Vector3(1.0, 1.0, 1.0)); + } + } + op_type = p_op_type; + emit_changed(); +} + +VisualShaderNodeParticleRandomness::OpType VisualShaderNodeParticleRandomness::get_op_type() const { + return op_type; +} + +VisualShaderNodeParticleRandomness::VisualShaderNodeParticleRandomness() { + set_input_port_default_value(0, 0.0); + set_input_port_default_value(1, 1.0); +} + +// VisualShaderNodeParticleAccelerator + +void VisualShaderNodeParticleAccelerator::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_mode", "mode"), &VisualShaderNodeParticleAccelerator::set_mode); + ClassDB::bind_method(D_METHOD("get_mode"), &VisualShaderNodeParticleAccelerator::get_mode); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Linear,Radial,Tangential"), "set_mode", "get_mode"); + + BIND_ENUM_CONSTANT(MODE_LINEAR); + BIND_ENUM_CONSTANT(MODE_RADIAL) + BIND_ENUM_CONSTANT(MODE_TANGENTIAL); + BIND_ENUM_CONSTANT(MODE_MAX); +} + +Vector<StringName> VisualShaderNodeParticleAccelerator::get_editable_properties() const { + Vector<StringName> props; + props.push_back("mode"); + return props; +} + +String VisualShaderNodeParticleAccelerator::get_caption() const { + return "ParticleAccelerator"; +} + +int VisualShaderNodeParticleAccelerator::get_output_port_count() const { + return 1; +} + +VisualShaderNodeParticleAccelerator::PortType VisualShaderNodeParticleAccelerator::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeParticleAccelerator::get_output_port_name(int p_port) const { + return String(); +} + +int VisualShaderNodeParticleAccelerator::get_input_port_count() const { + return 3; +} + +VisualShaderNodeParticleAccelerator::PortType VisualShaderNodeParticleAccelerator::get_input_port_type(int p_port) const { + if (p_port == 0) { + return PORT_TYPE_VECTOR; + } else if (p_port == 1) { + return PORT_TYPE_SCALAR; + } else if (p_port == 2) { + return PORT_TYPE_VECTOR; + } + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeParticleAccelerator::get_input_port_name(int p_port) const { + if (p_port == 0) { + return "amount"; + } else if (p_port == 1) { + return "randomness"; + } else if (p_port == 2) { + return "axis"; + } + return String(); +} + +String VisualShaderNodeParticleAccelerator::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + String code; + switch (mode) { + case MODE_LINEAR: + code += "\t" + p_output_vars[0] + " = length(VELOCITY) > 0.0 ? " + "normalize(VELOCITY) * " + (p_input_vars[0].is_empty() ? "vec3" + (String)get_input_port_default_value(0) : p_input_vars[0]) + " * mix(1.0, __rand_from_seed(__seed), " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ") : vec3(0.0);\n"; + break; + case MODE_RADIAL: + code += "\t" + p_output_vars[0] + " = length(__diff) > 0.0 ? __ndiff * " + (p_input_vars[0].is_empty() ? "vec3" + (String)get_input_port_default_value(0) : p_input_vars[0]) + " * mix(1.0, __rand_from_seed(__seed), " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ") : vec3(0.0);\n"; + break; + case MODE_TANGENTIAL: + code += "\t__vec3_buff1 = cross(__ndiff, normalize(" + (p_input_vars[2].is_empty() ? "vec3" + (String)get_input_port_default_value(2) : p_input_vars[2]) + "));\n"; + code += "\t" + p_output_vars[0] + " = length(__vec3_buff1) > 0.0 ? normalize(__vec3_buff1) * (" + (p_input_vars[0].is_empty() ? "vec3" + (String)get_input_port_default_value(0) : p_input_vars[0]) + " * mix(1.0, __rand_from_seed(__seed), " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ")) : vec3(0.0);\n"; + break; + case MODE_MAX: + break; + default: + break; + } + + return code; +} + +void VisualShaderNodeParticleAccelerator::set_mode(Mode p_mode) { + mode = p_mode; + emit_changed(); +} + +VisualShaderNodeParticleAccelerator::Mode VisualShaderNodeParticleAccelerator::get_mode() const { + return mode; +} + +VisualShaderNodeParticleAccelerator::VisualShaderNodeParticleAccelerator() { + set_input_port_default_value(0, Vector3(1, 1, 1)); + set_input_port_default_value(1, 0.0); + set_input_port_default_value(2, Vector3(0, -9.8, 0)); +} + +// VisualShaderNodeParticleOutput + +String VisualShaderNodeParticleOutput::get_caption() const { + if (shader_type == VisualShader::TYPE_START) { + return "StartOutput"; + } else if (shader_type == VisualShader::TYPE_PROCESS) { + return "ProcessOutput"; + } else if (shader_type == VisualShader::TYPE_COLLIDE) { + return "CollideOutput"; + } else if (shader_type == VisualShader::TYPE_START_CUSTOM) { + return "CustomStartOutput"; + } else if (shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { + return "CustomProcessOutput"; + } + return String(); +} + +int VisualShaderNodeParticleOutput::get_input_port_count() const { + if (shader_type == VisualShader::TYPE_START) { + return 8; + } else if (shader_type == VisualShader::TYPE_COLLIDE) { + return 5; + } else if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { + return 6; + } else { // TYPE_PROCESS + return 7; + } + return 0; +} + +VisualShaderNodeParticleOutput::PortType VisualShaderNodeParticleOutput::get_input_port_type(int p_port) const { + switch (p_port) { + case 0: + if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { + return PORT_TYPE_VECTOR; // custom.rgb + } + return PORT_TYPE_BOOLEAN; // active + case 1: + if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { + break; // custom.a (scalar) + } + return PORT_TYPE_VECTOR; // velocity + case 2: + return PORT_TYPE_VECTOR; // color & velocity + case 3: + if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { + return PORT_TYPE_VECTOR; // color + } + break; // alpha (scalar) + case 4: + if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { + break; // alpha + } + if (shader_type == VisualShader::TYPE_PROCESS) { + break; // scale + } + if (shader_type == VisualShader::TYPE_COLLIDE) { + return PORT_TYPE_TRANSFORM; // transform + } + return PORT_TYPE_VECTOR; // position + case 5: + if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { + return PORT_TYPE_TRANSFORM; // transform + } + if (shader_type == VisualShader::TYPE_PROCESS) { + return PORT_TYPE_VECTOR; // rotation_axis + } + break; // scale (scalar) + case 6: + if (shader_type == VisualShader::TYPE_START) { + return PORT_TYPE_VECTOR; // rotation_axis + } + break; + case 7: + break; // angle (scalar) + } + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeParticleOutput::get_input_port_name(int p_port) const { + String port_name; + switch (p_port) { + case 0: + if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { + port_name = "custom"; + break; + } + port_name = "active"; + break; + case 1: + if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { + port_name = "custom_alpha"; + break; + } + port_name = "velocity"; + break; + case 2: + if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { + port_name = "velocity"; + break; + } + port_name = "color"; + break; + case 3: + if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { + port_name = "color"; + break; + } + port_name = "alpha"; + break; + case 4: + if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { + port_name = "alpha"; + break; + } + if (shader_type == VisualShader::TYPE_PROCESS) { + port_name = "scale"; + break; + } + if (shader_type == VisualShader::TYPE_COLLIDE) { + port_name = "transform"; + break; + } + port_name = "position"; + break; + case 5: + if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { + port_name = "transform"; + break; + } + if (shader_type == VisualShader::TYPE_PROCESS) { + port_name = "rotation_axis"; + break; + } + port_name = "scale"; + break; + case 6: + if (shader_type == VisualShader::TYPE_PROCESS) { + port_name = "angle_in_radians"; + break; + } + port_name = "rotation_axis"; + break; + case 7: + port_name = "angle_in_radians"; + break; + default: + break; + } + if (!port_name.is_empty()) { + return port_name.capitalize(); + } + return String(); +} + +bool VisualShaderNodeParticleOutput::is_port_separator(int p_index) const { + if (shader_type == VisualShader::TYPE_START || shader_type == VisualShader::TYPE_PROCESS) { + String name = get_input_port_name(p_index); + return bool(name == "Scale"); + } + if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { + String name = get_input_port_name(p_index); + return bool(name == "Velocity"); + } + return false; +} + +String VisualShaderNodeParticleOutput::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + String code; + String tab = "\t"; + + if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { + if (!p_input_vars[0].is_empty()) { // custom.rgb + code += tab + "CUSTOM.rgb = " + p_input_vars[0] + ";\n"; + } + if (!p_input_vars[1].is_empty()) { // custom.a + code += tab + "CUSTOM.a = " + p_input_vars[1] + ";\n"; + } + if (!p_input_vars[2].is_empty()) { // velocity + code += tab + "VELOCITY = " + p_input_vars[2] + ";\n"; + } + if (!p_input_vars[3].is_empty()) { // color.rgb + code += tab + "COLOR.rgb = " + p_input_vars[3] + ";\n"; + } + if (!p_input_vars[4].is_empty()) { // color.a + code += tab + "COLOR.a = " + p_input_vars[4] + ";\n"; + } + if (!p_input_vars[5].is_empty()) { // transform + code += tab + "TRANSFORM = " + p_input_vars[5] + ";\n"; + } + } else { + if (!p_input_vars[0].is_empty()) { // active (begin) + code += tab + "ACTIVE = " + p_input_vars[0] + ";\n"; + code += tab + "if(ACTIVE) {\n"; + tab += "\t"; + } + if (!p_input_vars[1].is_empty()) { // velocity + code += tab + "VELOCITY = " + p_input_vars[1] + ";\n"; + } + if (!p_input_vars[2].is_empty()) { // color + code += tab + "COLOR.rgb = " + p_input_vars[2] + ";\n"; + } + if (!p_input_vars[3].is_empty()) { // alpha + code += tab + "COLOR.a = " + p_input_vars[3] + ";\n"; + } + + // position + if (shader_type == VisualShader::TYPE_START) { + code += tab + "if (RESTART_POSITION) {\n"; + if (!p_input_vars[4].is_empty()) { + code += tab + "\tTRANSFORM = mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(" + p_input_vars[4] + ", 1.0));\n"; + } else { + code += tab + "\tTRANSFORM = mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; + } + code += tab + "\tif (RESTART_VELOCITY) {\n"; + code += tab + "\t\tVELOCITY = (EMISSION_TRANSFORM * vec4(VELOCITY, 0.0)).xyz;\n"; + code += tab + "\t}\n"; + code += tab + "\tTRANSFORM = EMISSION_TRANSFORM * TRANSFORM;\n"; + code += tab + "}\n"; + } else if (shader_type == VisualShader::TYPE_COLLIDE) { // position + if (!p_input_vars[4].is_empty()) { + code += tab + "TRANSFORM = " + p_input_vars[4] + ";\n"; + } + } + + if (shader_type == VisualShader::TYPE_START || shader_type == VisualShader::TYPE_PROCESS) { + int scale = 5; + int rotation_axis = 6; + int rotation = 7; + if (shader_type == VisualShader::TYPE_PROCESS) { + scale = 4; + rotation_axis = 5; + rotation = 6; + } + String op; + if (shader_type == VisualShader::TYPE_START) { + op = "*="; + } else { + op = "="; + } + + if (!p_input_vars[rotation].is_empty()) { // rotation_axis & angle_in_radians + String axis; + if (p_input_vars[rotation_axis].is_empty()) { + axis = "vec3(0, 1, 0)"; + } else { + axis = p_input_vars[rotation_axis]; + } + code += tab + "TRANSFORM " + op + " __build_rotation_mat4(" + axis + ", " + p_input_vars[rotation] + ");\n"; + } + if (!p_input_vars[scale].is_empty()) { // scale + code += tab + "TRANSFORM " + op + " mat4(vec4(" + p_input_vars[scale] + ", 0, 0, 0), vec4(0, " + p_input_vars[scale] + ", 0, 0), vec4(0, 0, " + p_input_vars[scale] + ", 0), vec4(0, 0, 0, 1));\n"; + } + } + if (!p_input_vars[0].is_empty()) { // active (end) + code += "\t}\n"; + } + } + return code; +} + +VisualShaderNodeParticleOutput::VisualShaderNodeParticleOutput() { +} + +// EmitParticle + +Vector<StringName> VisualShaderNodeParticleEmit::get_editable_properties() const { + Vector<StringName> props; + props.push_back("flags"); + return props; +} + +void VisualShaderNodeParticleEmit::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_flags", "flags"), &VisualShaderNodeParticleEmit::set_flags); + ClassDB::bind_method(D_METHOD("get_flags"), &VisualShaderNodeParticleEmit::get_flags); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Position,RotScale,Velocity,Color,Custom"), "set_flags", "get_flags"); + + BIND_ENUM_CONSTANT(EMIT_FLAG_POSITION); + BIND_ENUM_CONSTANT(EMIT_FLAG_ROT_SCALE); + BIND_ENUM_CONSTANT(EMIT_FLAG_VELOCITY); + BIND_ENUM_CONSTANT(EMIT_FLAG_COLOR); + BIND_ENUM_CONSTANT(EMIT_FLAG_CUSTOM); +} + +String VisualShaderNodeParticleEmit::get_caption() const { + return "EmitParticle"; +} + +int VisualShaderNodeParticleEmit::get_input_port_count() const { + return 7; +} + +VisualShaderNodeParticleEmit::PortType VisualShaderNodeParticleEmit::get_input_port_type(int p_port) const { + switch (p_port) { + case 0: + return PORT_TYPE_BOOLEAN; + case 1: + return PORT_TYPE_TRANSFORM; + case 2: + return PORT_TYPE_VECTOR; + case 3: + return PORT_TYPE_VECTOR; + case 4: + return PORT_TYPE_SCALAR; + case 5: + return PORT_TYPE_VECTOR; + case 6: + return PORT_TYPE_SCALAR; + } + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeParticleEmit::get_input_port_name(int p_port) const { + switch (p_port) { + case 0: + return "condition"; + case 1: + return "transform"; + case 2: + return "velocity"; + case 3: + return "color"; + case 4: + return "alpha"; + case 5: + return "custom"; + case 6: + return "custom_alpha"; + } + return String(); +} + +int VisualShaderNodeParticleEmit::get_output_port_count() const { + return 0; +} + +VisualShaderNodeParticleEmit::PortType VisualShaderNodeParticleEmit::get_output_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeParticleEmit::get_output_port_name(int p_port) const { + return String(); +} + +void VisualShaderNodeParticleEmit::add_flag(EmitFlags p_flag) { + flags |= p_flag; + emit_changed(); +} + +bool VisualShaderNodeParticleEmit::has_flag(EmitFlags p_flag) const { + return flags & p_flag; +} + +void VisualShaderNodeParticleEmit::set_flags(EmitFlags p_flags) { + flags = (int)p_flags; + emit_changed(); +} + +VisualShaderNodeParticleEmit::EmitFlags VisualShaderNodeParticleEmit::get_flags() const { + return EmitFlags(flags); +} + +bool VisualShaderNodeParticleEmit::is_show_prop_names() const { + return true; +} + +bool VisualShaderNodeParticleEmit::is_generate_input_var(int p_port) const { + if (p_port == 0) { + if (!is_input_port_connected(0)) { + return false; + } + } + return true; +} + +String VisualShaderNodeParticleEmit::get_input_port_default_hint(int p_port) const { + switch (p_port) { + case 1: + return "default"; + case 2: + return "default"; + case 3: + return "default"; + case 4: + return "default"; + case 5: + return "default"; + case 6: + return "default"; + } + return String(); +} + +String VisualShaderNodeParticleEmit::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + String code; + String tab; + bool default_condition = false; + + if (!is_input_port_connected(0)) { + default_condition = true; + if (get_input_port_default_value(0)) { + tab = "\t"; + } else { + return code; + } + } else { + tab = "\t\t"; + } + + String transform; + if (p_input_vars[1].is_empty()) { + transform = "TRANSFORM"; + } else { + transform = p_input_vars[1]; + } + + String velocity; + if (p_input_vars[2].is_empty()) { + velocity = "VELOCITY"; + } else { + velocity = p_input_vars[2]; + } + + String color; + if (p_input_vars[3].is_empty()) { + color = "COLOR.rgb"; + } else { + color = p_input_vars[3]; + } + + String alpha; + if (p_input_vars[4].is_empty()) { + alpha = "COLOR.a"; + } else { + alpha = p_input_vars[4]; + } + + String custom; + if (p_input_vars[5].is_empty()) { + custom = "CUSTOM.rgb"; + } else { + custom = p_input_vars[5]; + } + + String custom_alpha; + if (p_input_vars[6].is_empty()) { + custom_alpha = "CUSTOM.a"; + } else { + custom_alpha = p_input_vars[6]; + } + + List<String> flags_arr; + + if (has_flag(EmitFlags::EMIT_FLAG_POSITION)) { + flags_arr.push_back("FLAG_EMIT_POSITION"); + } + if (has_flag(EmitFlags::EMIT_FLAG_ROT_SCALE)) { + flags_arr.push_back("FLAG_EMIT_ROT_SCALE"); + } + if (has_flag(EmitFlags::EMIT_FLAG_VELOCITY)) { + flags_arr.push_back("FLAG_EMIT_VELOCITY"); + } + if (has_flag(EmitFlags::EMIT_FLAG_COLOR)) { + flags_arr.push_back("FLAG_EMIT_COLOR"); + } + if (has_flag(EmitFlags::EMIT_FLAG_CUSTOM)) { + flags_arr.push_back("FLAG_EMIT_CUSTOM"); + } + + String flags; + + for (int i = 0; i < flags_arr.size(); i++) { + if (i > 0) { + flags += "|"; + } + flags += flags_arr[i]; + } + + if (flags.is_empty()) { + flags = "uint(0)"; + } + + if (!default_condition) { + code += "\tif (" + p_input_vars[0] + ") {\n"; + } + + code += tab + "emit_subparticle(" + transform + ", " + velocity + ", vec4(" + color + ", " + alpha + "), vec4(" + custom + ", " + custom_alpha + "), " + flags + ");\n"; + + if (!default_condition) { + code += "\t}\n"; + } + + return code; +} + +VisualShaderNodeParticleEmit::VisualShaderNodeParticleEmit() { + set_input_port_default_value(0, true); +} diff --git a/scene/resources/visual_shader_particle_nodes.h b/scene/resources/visual_shader_particle_nodes.h new file mode 100644 index 0000000000..ecd187a885 --- /dev/null +++ b/scene/resources/visual_shader_particle_nodes.h @@ -0,0 +1,285 @@ +/*************************************************************************/ +/* visual_shader_particle_nodes.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef VISUAL_SHADER_PARTICLE_NODES_H +#define VISUAL_SHADER_PARTICLE_NODES_H + +#include "scene/resources/visual_shader.h" + +// Emit nodes + +class VisualShaderNodeParticleEmitter : public VisualShaderNode { + GDCLASS(VisualShaderNodeParticleEmitter, VisualShaderNode); + +public: + virtual int get_output_port_count() const override; + virtual PortType get_output_port_type(int p_port) const override; + virtual String get_output_port_name(int p_port) const override; + + VisualShaderNodeParticleEmitter(); +}; + +class VisualShaderNodeParticleSphereEmitter : public VisualShaderNodeParticleEmitter { + GDCLASS(VisualShaderNodeParticleSphereEmitter, VisualShaderNodeParticleEmitter); + +public: + virtual String get_caption() const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + + virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + VisualShaderNodeParticleSphereEmitter(); +}; + +class VisualShaderNodeParticleBoxEmitter : public VisualShaderNodeParticleEmitter { + GDCLASS(VisualShaderNodeParticleBoxEmitter, VisualShaderNodeParticleEmitter); + +public: + virtual String get_caption() const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + + virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + VisualShaderNodeParticleBoxEmitter(); +}; + +class VisualShaderNodeParticleRingEmitter : public VisualShaderNodeParticleEmitter { + GDCLASS(VisualShaderNodeParticleRingEmitter, VisualShaderNodeParticleEmitter); + +public: + virtual String get_caption() const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + + virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + VisualShaderNodeParticleRingEmitter(); +}; + +class VisualShaderNodeParticleMultiplyByAxisAngle : public VisualShaderNode { + GDCLASS(VisualShaderNodeParticleMultiplyByAxisAngle, VisualShaderNode); + bool degrees_mode = true; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + virtual bool is_show_prop_names() const override; + + virtual int get_output_port_count() const override; + virtual PortType get_output_port_type(int p_port) const override; + virtual String get_output_port_name(int p_port) const override; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + void set_degrees_mode(bool p_enabled); + bool is_degrees_mode() const; + Vector<StringName> get_editable_properties() const override; + + VisualShaderNodeParticleMultiplyByAxisAngle(); +}; + +class VisualShaderNodeParticleConeVelocity : public VisualShaderNode { + GDCLASS(VisualShaderNodeParticleConeVelocity, VisualShaderNode); + +public: + virtual String get_caption() const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + + virtual int get_output_port_count() const override; + virtual PortType get_output_port_type(int p_port) const override; + virtual String get_output_port_name(int p_port) const override; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + VisualShaderNodeParticleConeVelocity(); +}; + +class VisualShaderNodeParticleRandomness : public VisualShaderNode { + GDCLASS(VisualShaderNodeParticleRandomness, VisualShaderNode); + +public: + enum OpType { + OP_TYPE_SCALAR, + OP_TYPE_VECTOR, + OP_TYPE_MAX, + }; + +private: + OpType op_type = OP_TYPE_SCALAR; + +protected: + static void _bind_methods(); + +public: + Vector<StringName> get_editable_properties() const override; + virtual String get_caption() const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + + virtual int get_output_port_count() const override; + virtual PortType get_output_port_type(int p_port) const override; + virtual String get_output_port_name(int p_port) const override; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + void set_op_type(OpType p_type); + OpType get_op_type() const; + + VisualShaderNodeParticleRandomness(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeParticleRandomness::OpType) + +// Process nodes + +class VisualShaderNodeParticleAccelerator : public VisualShaderNodeOutput { + GDCLASS(VisualShaderNodeParticleAccelerator, VisualShaderNodeOutput); + +public: + enum Mode { + MODE_LINEAR, + MODE_RADIAL, + MODE_TANGENTIAL, + MODE_MAX, + }; + +private: + Mode mode = MODE_LINEAR; + +protected: + static void _bind_methods(); + +public: + Vector<StringName> get_editable_properties() const override; + virtual String get_caption() const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + + virtual int get_output_port_count() const override; + virtual PortType get_output_port_type(int p_port) const override; + virtual String get_output_port_name(int p_port) const override; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + void set_mode(Mode p_mode); + Mode get_mode() const; + + VisualShaderNodeParticleAccelerator(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeParticleAccelerator::Mode) + +// Common nodes + +class VisualShaderNodeParticleOutput : public VisualShaderNodeOutput { + GDCLASS(VisualShaderNodeParticleOutput, VisualShaderNodeOutput); + +public: + virtual String get_caption() const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + virtual bool is_port_separator(int p_index) const override; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + VisualShaderNodeParticleOutput(); +}; + +class VisualShaderNodeParticleEmit : public VisualShaderNode { + GDCLASS(VisualShaderNodeParticleEmit, VisualShaderNode); + +public: + enum EmitFlags { + EMIT_FLAG_POSITION = 1, + EMIT_FLAG_ROT_SCALE = 2, + EMIT_FLAG_VELOCITY = 4, + EMIT_FLAG_COLOR = 8, + EMIT_FLAG_CUSTOM = 16, + }; + +protected: + int flags = EMIT_FLAG_POSITION | EMIT_FLAG_ROT_SCALE | EMIT_FLAG_VELOCITY | EMIT_FLAG_COLOR | EMIT_FLAG_CUSTOM; + static void _bind_methods(); + +public: + Vector<StringName> get_editable_properties() const override; + virtual String get_caption() const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + + virtual int get_output_port_count() const override; + virtual PortType get_output_port_type(int p_port) const override; + virtual String get_output_port_name(int p_port) const override; + + void add_flag(EmitFlags p_flag); + bool has_flag(EmitFlags p_flag) const; + + void set_flags(EmitFlags p_flags); + EmitFlags get_flags() const; + + virtual bool is_show_prop_names() const override; + virtual bool is_generate_input_var(int p_port) const override; + virtual String get_input_port_default_hint(int p_port) const override; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + VisualShaderNodeParticleEmit(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeParticleEmit::EmitFlags) + +#endif diff --git a/scene/resources/world_2d.cpp b/scene/resources/world_2d.cpp index ccdc5bebd0..eceb42ee14 100644 --- a/scene/resources/world_2d.cpp +++ b/scene/resources/world_2d.cpp @@ -32,290 +32,12 @@ #include "core/config/project_settings.h" #include "scene/2d/camera_2d.h" -#include "scene/2d/visibility_notifier_2d.h" +#include "scene/2d/visible_on_screen_notifier_2d.h" #include "scene/main/window.h" #include "servers/navigation_server_2d.h" #include "servers/physics_server_2d.h" #include "servers/rendering_server.h" -struct SpatialIndexer2D { - struct CellRef { - int ref = 0; - - _FORCE_INLINE_ int inc() { - ref++; - return ref; - } - _FORCE_INLINE_ int dec() { - ref--; - return ref; - } - }; - - struct CellKey { - union { - struct { - int32_t x; - int32_t y; - }; - uint64_t key = 0; - }; - - bool operator==(const CellKey &p_key) const { return key == p_key.key; } - _FORCE_INLINE_ bool operator<(const CellKey &p_key) const { - return key < p_key.key; - } - }; - - struct CellData { - Map<VisibilityNotifier2D *, CellRef> notifiers; - }; - - Map<CellKey, CellData> cells; - int cell_size; - - Map<VisibilityNotifier2D *, Rect2> notifiers; - - struct ViewportData { - Map<VisibilityNotifier2D *, uint64_t> notifiers; - Rect2 rect; - }; - - Map<Viewport *, ViewportData> viewports; - - bool changed = false; - - uint64_t pass = 0; - - void _notifier_update_cells(VisibilityNotifier2D *p_notifier, const Rect2 &p_rect, bool p_add) { - Point2i begin = p_rect.position; - begin /= cell_size; - Point2i end = p_rect.position + p_rect.size; - end /= cell_size; - for (int i = begin.x; i <= end.x; i++) { - for (int j = begin.y; j <= end.y; j++) { - CellKey ck; - ck.x = i; - ck.y = j; - Map<CellKey, CellData>::Element *E = cells.find(ck); - - if (p_add) { - if (!E) { - E = cells.insert(ck, CellData()); - } - E->get().notifiers[p_notifier].inc(); - } else { - ERR_CONTINUE(!E); - if (E->get().notifiers[p_notifier].dec() == 0) { - E->get().notifiers.erase(p_notifier); - if (E->get().notifiers.is_empty()) { - cells.erase(E); - } - } - } - } - } - } - - void _notifier_add(VisibilityNotifier2D *p_notifier, const Rect2 &p_rect) { - ERR_FAIL_COND(notifiers.has(p_notifier)); - notifiers[p_notifier] = p_rect; - _notifier_update_cells(p_notifier, p_rect, true); - changed = true; - } - - void _notifier_update(VisibilityNotifier2D *p_notifier, const Rect2 &p_rect) { - Map<VisibilityNotifier2D *, Rect2>::Element *E = notifiers.find(p_notifier); - ERR_FAIL_COND(!E); - if (E->get() == p_rect) { - return; - } - - _notifier_update_cells(p_notifier, p_rect, true); - _notifier_update_cells(p_notifier, E->get(), false); - E->get() = p_rect; - changed = true; - } - - void _notifier_remove(VisibilityNotifier2D *p_notifier) { - Map<VisibilityNotifier2D *, Rect2>::Element *E = notifiers.find(p_notifier); - ERR_FAIL_COND(!E); - _notifier_update_cells(p_notifier, E->get(), false); - notifiers.erase(p_notifier); - - List<Viewport *> removed; - for (Map<Viewport *, ViewportData>::Element *F = viewports.front(); F; F = F->next()) { - Map<VisibilityNotifier2D *, uint64_t>::Element *G = F->get().notifiers.find(p_notifier); - - if (G) { - F->get().notifiers.erase(G); - removed.push_back(F->key()); - } - } - - while (!removed.is_empty()) { - p_notifier->_exit_viewport(removed.front()->get()); - removed.pop_front(); - } - - changed = true; - } - - void _add_viewport(Viewport *p_viewport, const Rect2 &p_rect) { - ERR_FAIL_COND(viewports.has(p_viewport)); - ViewportData vd; - vd.rect = p_rect; - viewports[p_viewport] = vd; - changed = true; - } - - void _update_viewport(Viewport *p_viewport, const Rect2 &p_rect) { - Map<Viewport *, ViewportData>::Element *E = viewports.find(p_viewport); - ERR_FAIL_COND(!E); - if (E->get().rect == p_rect) { - return; - } - E->get().rect = p_rect; - changed = true; - } - - void _remove_viewport(Viewport *p_viewport) { - ERR_FAIL_COND(!viewports.has(p_viewport)); - List<VisibilityNotifier2D *> removed; - for (Map<VisibilityNotifier2D *, uint64_t>::Element *E = viewports[p_viewport].notifiers.front(); E; E = E->next()) { - removed.push_back(E->key()); - } - - while (!removed.is_empty()) { - removed.front()->get()->_exit_viewport(p_viewport); - removed.pop_front(); - } - - viewports.erase(p_viewport); - } - - void _update() { - if (!changed) { - return; - } - - for (Map<Viewport *, ViewportData>::Element *E = viewports.front(); E; E = E->next()) { - Point2i begin = E->get().rect.position; - begin /= cell_size; - Point2i end = E->get().rect.position + E->get().rect.size; - end /= cell_size; - pass++; - List<VisibilityNotifier2D *> added; - List<VisibilityNotifier2D *> removed; - - uint64_t visible_cells = (uint64_t)(end.x - begin.x) * (uint64_t)(end.y - begin.y); - - if (visible_cells > 10000) { - //well you zoomed out a lot, it's your problem. To avoid freezing in the for loops below, we'll manually check cell by cell - - for (Map<CellKey, CellData>::Element *F = cells.front(); F; F = F->next()) { - const CellKey &ck = F->key(); - - if (ck.x < begin.x || ck.x > end.x) { - continue; - } - if (ck.y < begin.y || ck.y > end.y) { - continue; - } - - //notifiers in cell - for (Map<VisibilityNotifier2D *, CellRef>::Element *G = F->get().notifiers.front(); G; G = G->next()) { - Map<VisibilityNotifier2D *, uint64_t>::Element *H = E->get().notifiers.find(G->key()); - if (!H) { - H = E->get().notifiers.insert(G->key(), pass); - added.push_back(G->key()); - } else { - H->get() = pass; - } - } - } - - } else { - //check cells in grid fashion - for (int i = begin.x; i <= end.x; i++) { - for (int j = begin.y; j <= end.y; j++) { - CellKey ck; - ck.x = i; - ck.y = j; - - Map<CellKey, CellData>::Element *F = cells.find(ck); - if (!F) { - continue; - } - - //notifiers in cell - for (Map<VisibilityNotifier2D *, CellRef>::Element *G = F->get().notifiers.front(); G; G = G->next()) { - Map<VisibilityNotifier2D *, uint64_t>::Element *H = E->get().notifiers.find(G->key()); - if (!H) { - H = E->get().notifiers.insert(G->key(), pass); - added.push_back(G->key()); - } else { - H->get() = pass; - } - } - } - } - } - - for (Map<VisibilityNotifier2D *, uint64_t>::Element *F = E->get().notifiers.front(); F; F = F->next()) { - if (F->get() != pass) { - removed.push_back(F->key()); - } - } - - while (!added.is_empty()) { - added.front()->get()->_enter_viewport(E->key()); - added.pop_front(); - } - - while (!removed.is_empty()) { - E->get().notifiers.erase(removed.front()->get()); - removed.front()->get()->_exit_viewport(E->key()); - removed.pop_front(); - } - } - - changed = false; - } - - SpatialIndexer2D() { - cell_size = GLOBAL_DEF("world/2d/cell_size", 100); - } -}; - -void World2D::_register_viewport(Viewport *p_viewport, const Rect2 &p_rect) { - indexer->_add_viewport(p_viewport, p_rect); -} - -void World2D::_update_viewport(Viewport *p_viewport, const Rect2 &p_rect) { - indexer->_update_viewport(p_viewport, p_rect); -} - -void World2D::_remove_viewport(Viewport *p_viewport) { - indexer->_remove_viewport(p_viewport); -} - -void World2D::_register_notifier(VisibilityNotifier2D *p_notifier, const Rect2 &p_rect) { - indexer->_notifier_add(p_notifier, p_rect); -} - -void World2D::_update_notifier(VisibilityNotifier2D *p_notifier, const Rect2 &p_rect) { - indexer->_notifier_update(p_notifier, p_rect); -} - -void World2D::_remove_notifier(VisibilityNotifier2D *p_notifier) { - indexer->_notifier_remove(p_notifier); -} - -void World2D::_update() { - indexer->_update(); -} - RID World2D::get_canvas() const { return canvas; } @@ -328,12 +50,6 @@ RID World2D::get_navigation_map() const { return navigation_map; } -void World2D::get_viewport_list(List<Viewport *> *r_viewports) { - for (Map<Viewport *, SpatialIndexer2D::ViewportData>::Element *E = indexer->viewports.front(); E; E = E->next()) { - r_viewports->push_back(E->key()); - } -} - void World2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_canvas"), &World2D::get_canvas); ClassDB::bind_method(D_METHOD("get_space"), &World2D::get_space); @@ -341,10 +57,10 @@ void World2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_direct_space_state"), &World2D::get_direct_space_state); - ADD_PROPERTY(PropertyInfo(Variant::RID, "canvas", PROPERTY_HINT_NONE, "", 0), "", "get_canvas"); - ADD_PROPERTY(PropertyInfo(Variant::RID, "space", PROPERTY_HINT_NONE, "", 0), "", "get_space"); - ADD_PROPERTY(PropertyInfo(Variant::RID, "navigation_map", PROPERTY_HINT_NONE, "", 0), "", "get_navigation_map"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "direct_space_state", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsDirectSpaceState2D", 0), "", "get_direct_space_state"); + ADD_PROPERTY(PropertyInfo(Variant::RID, "canvas", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_canvas"); + ADD_PROPERTY(PropertyInfo(Variant::RID, "space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_space"); + ADD_PROPERTY(PropertyInfo(Variant::RID, "navigation_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_navigation_map"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "direct_space_state", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsDirectSpaceState2D", PROPERTY_USAGE_NONE), "", "get_direct_space_state"); } PhysicsDirectSpaceState2D *World2D::get_direct_space_state() { @@ -357,7 +73,7 @@ World2D::World2D() { // Create and configure space2D to be more friendly with pixels than meters space = PhysicsServer2D::get_singleton()->space_create(); PhysicsServer2D::get_singleton()->space_set_active(space, true); - PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY, GLOBAL_DEF("physics/2d/default_gravity", 98)); + PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY, GLOBAL_DEF("physics/2d/default_gravity", 980.0)); PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY_VECTOR, GLOBAL_DEF("physics/2d/default_gravity_vector", Vector2(0, 1))); PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_LINEAR_DAMP, GLOBAL_DEF("physics/2d/default_linear_damp", 0.1)); ProjectSettings::get_singleton()->set_custom_property_info("physics/2d/default_linear_damp", PropertyInfo(Variant::FLOAT, "physics/2d/default_linear_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater")); @@ -369,13 +85,10 @@ World2D::World2D() { NavigationServer2D::get_singleton()->map_set_active(navigation_map, true); NavigationServer2D::get_singleton()->map_set_cell_size(navigation_map, GLOBAL_DEF("navigation/2d/default_cell_size", 10)); NavigationServer2D::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_DEF("navigation/2d/default_edge_connection_margin", 5)); - - indexer = memnew(SpatialIndexer2D); } World2D::~World2D() { RenderingServer::get_singleton()->free(canvas); PhysicsServer2D::get_singleton()->free(space); NavigationServer2D::get_singleton()->free(navigation_map); - memdelete(indexer); } diff --git a/scene/resources/world_2d.h b/scene/resources/world_2d.h index 38abf3d7ad..65f89c8f64 100644 --- a/scene/resources/world_2d.h +++ b/scene/resources/world_2d.h @@ -35,7 +35,7 @@ #include "core/io/resource.h" #include "servers/physics_server_2d.h" -class VisibilityNotifier2D; +class VisibleOnScreenNotifier2D; class Viewport; struct SpatialIndexer2D; @@ -46,23 +46,15 @@ class World2D : public Resource { RID space; RID navigation_map; - SpatialIndexer2D *indexer; + Set<Viewport *> viewports; protected: static void _bind_methods(); friend class Viewport; - friend class VisibilityNotifier2D; - void _register_viewport(Viewport *p_viewport, const Rect2 &p_rect); - void _update_viewport(Viewport *p_viewport, const Rect2 &p_rect); + void _register_viewport(Viewport *p_viewport); void _remove_viewport(Viewport *p_viewport); - void _register_notifier(VisibilityNotifier2D *p_notifier, const Rect2 &p_rect); - void _update_notifier(VisibilityNotifier2D *p_notifier, const Rect2 &p_rect); - void _remove_notifier(VisibilityNotifier2D *p_notifier); - - void _update(); - public: RID get_canvas() const; RID get_space() const; @@ -70,7 +62,7 @@ public: PhysicsDirectSpaceState2D *get_direct_space_state(); - void get_viewport_list(List<Viewport *> *r_viewports); + _FORCE_INLINE_ const Set<Viewport *> &get_viewports() { return viewports; } World2D(); ~World2D(); diff --git a/scene/resources/world_3d.cpp b/scene/resources/world_3d.cpp index 0e9f7a6cf2..42047f104f 100644 --- a/scene/resources/world_3d.cpp +++ b/scene/resources/world_3d.cpp @@ -33,210 +33,19 @@ #include "core/math/camera_matrix.h" #include "core/math/octree.h" #include "scene/3d/camera_3d.h" -#include "scene/3d/visibility_notifier_3d.h" +#include "scene/3d/visible_on_screen_notifier_3d.h" #include "scene/scene_string_names.h" #include "servers/navigation_server_3d.h" -struct SpatialIndexer { - Octree<VisibilityNotifier3D> octree; - - struct NotifierData { - AABB aabb; - OctreeElementID id; - }; - - Map<VisibilityNotifier3D *, NotifierData> notifiers; - struct CameraData { - Map<VisibilityNotifier3D *, uint64_t> notifiers; - }; - - Map<Camera3D *, CameraData> cameras; - - enum { - VISIBILITY_CULL_MAX = 32768 - }; - - Vector<VisibilityNotifier3D *> cull; - - bool changed; - uint64_t pass; - uint64_t last_frame; - - void _notifier_add(VisibilityNotifier3D *p_notifier, const AABB &p_rect) { - ERR_FAIL_COND(notifiers.has(p_notifier)); - notifiers[p_notifier].aabb = p_rect; - notifiers[p_notifier].id = octree.create(p_notifier, p_rect); - changed = true; - } - - void _notifier_update(VisibilityNotifier3D *p_notifier, const AABB &p_rect) { - Map<VisibilityNotifier3D *, NotifierData>::Element *E = notifiers.find(p_notifier); - ERR_FAIL_COND(!E); - if (E->get().aabb == p_rect) { - return; - } - - E->get().aabb = p_rect; - octree.move(E->get().id, E->get().aabb); - changed = true; - } - - void _notifier_remove(VisibilityNotifier3D *p_notifier) { - Map<VisibilityNotifier3D *, NotifierData>::Element *E = notifiers.find(p_notifier); - ERR_FAIL_COND(!E); - - octree.erase(E->get().id); - notifiers.erase(p_notifier); - - List<Camera3D *> removed; - for (Map<Camera3D *, CameraData>::Element *F = cameras.front(); F; F = F->next()) { - Map<VisibilityNotifier3D *, uint64_t>::Element *G = F->get().notifiers.find(p_notifier); - - if (G) { - F->get().notifiers.erase(G); - removed.push_back(F->key()); - } - } - - while (!removed.is_empty()) { - p_notifier->_exit_camera(removed.front()->get()); - removed.pop_front(); - } - - changed = true; - } - - void _add_camera(Camera3D *p_camera) { - ERR_FAIL_COND(cameras.has(p_camera)); - CameraData vd; - cameras[p_camera] = vd; - changed = true; - } - - void _update_camera(Camera3D *p_camera) { - Map<Camera3D *, CameraData>::Element *E = cameras.find(p_camera); - ERR_FAIL_COND(!E); - changed = true; - } - - void _remove_camera(Camera3D *p_camera) { - ERR_FAIL_COND(!cameras.has(p_camera)); - List<VisibilityNotifier3D *> removed; - for (Map<VisibilityNotifier3D *, uint64_t>::Element *E = cameras[p_camera].notifiers.front(); E; E = E->next()) { - removed.push_back(E->key()); - } - - while (!removed.is_empty()) { - removed.front()->get()->_exit_camera(p_camera); - removed.pop_front(); - } - - cameras.erase(p_camera); - } - - void _update(uint64_t p_frame) { - if (p_frame == last_frame) { - return; - } - last_frame = p_frame; - - if (!changed) { - return; - } - - for (Map<Camera3D *, CameraData>::Element *E = cameras.front(); E; E = E->next()) { - pass++; - - Camera3D *c = E->key(); - - Vector<Plane> planes = c->get_frustum(); - - int culled = octree.cull_convex(planes, cull.ptrw(), cull.size()); - - VisibilityNotifier3D **ptr = cull.ptrw(); - - List<VisibilityNotifier3D *> added; - List<VisibilityNotifier3D *> removed; - - for (int i = 0; i < culled; i++) { - //notifiers in frustum - - Map<VisibilityNotifier3D *, uint64_t>::Element *H = E->get().notifiers.find(ptr[i]); - if (!H) { - E->get().notifiers.insert(ptr[i], pass); - added.push_back(ptr[i]); - } else { - H->get() = pass; - } - } - - for (Map<VisibilityNotifier3D *, uint64_t>::Element *F = E->get().notifiers.front(); F; F = F->next()) { - if (F->get() != pass) { - removed.push_back(F->key()); - } - } - - while (!added.is_empty()) { - added.front()->get()->_enter_camera(E->key()); - added.pop_front(); - } - - while (!removed.is_empty()) { - E->get().notifiers.erase(removed.front()->get()); - removed.front()->get()->_exit_camera(E->key()); - removed.pop_front(); - } - } - changed = false; - } - - SpatialIndexer() { - pass = 0; - last_frame = 0; - changed = false; - cull.resize(VISIBILITY_CULL_MAX); - } -}; - void World3D::_register_camera(Camera3D *p_camera) { #ifndef _3D_DISABLED - indexer->_add_camera(p_camera); -#endif -} - -void World3D::_update_camera(Camera3D *p_camera) { -#ifndef _3D_DISABLED - indexer->_update_camera(p_camera); + cameras.insert(p_camera); #endif } void World3D::_remove_camera(Camera3D *p_camera) { #ifndef _3D_DISABLED - indexer->_remove_camera(p_camera); -#endif -} - -void World3D::_register_notifier(VisibilityNotifier3D *p_notifier, const AABB &p_rect) { -#ifndef _3D_DISABLED - indexer->_notifier_add(p_notifier, p_rect); -#endif -} - -void World3D::_update_notifier(VisibilityNotifier3D *p_notifier, const AABB &p_rect) { -#ifndef _3D_DISABLED - indexer->_notifier_update(p_notifier, p_rect); -#endif -} - -void World3D::_remove_notifier(VisibilityNotifier3D *p_notifier) { -#ifndef _3D_DISABLED - indexer->_notifier_remove(p_notifier); -#endif -} - -void World3D::_update(uint64_t p_frame) { -#ifndef _3D_DISABLED - indexer->_update(p_frame); + cameras.erase(p_camera); #endif } @@ -307,12 +116,6 @@ PhysicsDirectSpaceState3D *World3D::get_direct_space_state() { return PhysicsServer3D::get_singleton()->space_get_direct_state(space); } -void World3D::get_camera_list(List<Camera3D *> *r_cameras) { - for (Map<Camera3D *, SpatialIndexer::CameraData>::Element *E = indexer->cameras.front(); E; E = E->next()) { - r_cameras->push_back(E->key()); - } -} - void World3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_space"), &World3D::get_space); ClassDB::bind_method(D_METHOD("get_navigation_map"), &World3D::get_navigation_map); @@ -321,16 +124,16 @@ void World3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_environment"), &World3D::get_environment); ClassDB::bind_method(D_METHOD("set_fallback_environment", "env"), &World3D::set_fallback_environment); ClassDB::bind_method(D_METHOD("get_fallback_environment"), &World3D::get_fallback_environment); - ClassDB::bind_method(D_METHOD("set_camera_effects", "env"), &World3D::set_camera_effects); + ClassDB::bind_method(D_METHOD("set_camera_effects", "effects"), &World3D::set_camera_effects); ClassDB::bind_method(D_METHOD("get_camera_effects"), &World3D::get_camera_effects); ClassDB::bind_method(D_METHOD("get_direct_space_state"), &World3D::get_direct_space_state); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "environment", PROPERTY_HINT_RESOURCE_TYPE, "Environment"), "set_environment", "get_environment"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "fallback_environment", PROPERTY_HINT_RESOURCE_TYPE, "Environment"), "set_fallback_environment", "get_fallback_environment"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "camera_effects", PROPERTY_HINT_RESOURCE_TYPE, "CameraEffects"), "set_camera_effects", "get_camera_effects"); - ADD_PROPERTY(PropertyInfo(Variant::RID, "space", PROPERTY_HINT_NONE, "", 0), "", "get_space"); - ADD_PROPERTY(PropertyInfo(Variant::RID, "navigation_map", PROPERTY_HINT_NONE, "", 0), "", "get_navigation_map"); - ADD_PROPERTY(PropertyInfo(Variant::RID, "scenario", PROPERTY_HINT_NONE, "", 0), "", "get_scenario"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "direct_space_state", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsDirectSpaceState3D", 0), "", "get_direct_space_state"); + ADD_PROPERTY(PropertyInfo(Variant::RID, "space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_space"); + ADD_PROPERTY(PropertyInfo(Variant::RID, "navigation_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_navigation_map"); + ADD_PROPERTY(PropertyInfo(Variant::RID, "scenario", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_scenario"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "direct_space_state", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsDirectSpaceState3D", PROPERTY_USAGE_NONE), "", "get_direct_space_state"); } World3D::World3D() { @@ -348,21 +151,11 @@ World3D::World3D() { navigation_map = NavigationServer3D::get_singleton()->map_create(); NavigationServer3D::get_singleton()->map_set_active(navigation_map, true); NavigationServer3D::get_singleton()->map_set_cell_size(navigation_map, GLOBAL_DEF("navigation/3d/default_cell_size", 0.3)); - NavigationServer3D::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_DEF("navigation/3d/default_edge_connection_margin", 5.0)); // Five meters, depends a lot on the agent's radius - -#ifdef _3D_DISABLED - indexer = nullptr; -#else - indexer = memnew(SpatialIndexer); -#endif + NavigationServer3D::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_DEF("navigation/3d/default_edge_connection_margin", 0.3)); } World3D::~World3D() { PhysicsServer3D::get_singleton()->free(space); RenderingServer::get_singleton()->free(scenario); NavigationServer3D::get_singleton()->free(navigation_map); - -#ifndef _3D_DISABLED - memdelete(indexer); -#endif } diff --git a/scene/resources/world_3d.h b/scene/resources/world_3d.h index 4e2717a2bb..2c5be35609 100644 --- a/scene/resources/world_3d.h +++ b/scene/resources/world_3d.h @@ -38,7 +38,7 @@ #include "servers/rendering_server.h" class Camera3D; -class VisibilityNotifier3D; +class VisibleOnScreenNotifier3D; struct SpatialIndexer; class World3D : public Resource { @@ -48,27 +48,21 @@ private: RID space; RID navigation_map; RID scenario; - SpatialIndexer *indexer; + Ref<Environment> environment; Ref<Environment> fallback_environment; Ref<CameraEffects> camera_effects; + Set<Camera3D *> cameras; + protected: static void _bind_methods(); friend class Camera3D; - friend class VisibilityNotifier3D; void _register_camera(Camera3D *p_camera); - void _update_camera(Camera3D *p_camera); void _remove_camera(Camera3D *p_camera); - void _register_notifier(VisibilityNotifier3D *p_notifier, const AABB &p_rect); - void _update_notifier(VisibilityNotifier3D *p_notifier, const AABB &p_rect); - void _remove_notifier(VisibilityNotifier3D *p_notifier); - friend class Viewport; - void _update(uint64_t p_frame); - public: RID get_space() const; RID get_navigation_map() const; @@ -83,7 +77,7 @@ public: void set_camera_effects(const Ref<CameraEffects> &p_camera_effects); Ref<CameraEffects> get_camera_effects() const; - void get_camera_list(List<Camera3D *> *r_cameras); + _FORCE_INLINE_ const Set<Camera3D *> &get_cameras() const { return cameras; } PhysicsDirectSpaceState3D *get_direct_space_state(); |