diff options
Diffstat (limited to 'modules')
32 files changed, 821 insertions, 889 deletions
diff --git a/modules/bullet/SCsub b/modules/bullet/SCsub index bfac0df5b0..ba57de303e 100644 --- a/modules/bullet/SCsub +++ b/modules/bullet/SCsub @@ -12,6 +12,8 @@ thirdparty_obj = [] if env["builtin_bullet"]: # Build only version 2 for now (as of 2.89) # Sync file list with relevant upstream CMakeLists.txt for each folder. + if env["float"] == "64": + env.Append(CPPDEFINES=["BT_USE_DOUBLE_PRECISION=1"]) thirdparty_dir = "#thirdparty/bullet/" bullet2_src = [ diff --git a/modules/bullet/bullet_physics_server.cpp b/modules/bullet/bullet_physics_server.cpp index e07be14c5b..fc876a81cf 100644 --- a/modules/bullet/bullet_physics_server.cpp +++ b/modules/bullet/bullet_physics_server.cpp @@ -842,12 +842,12 @@ PhysicsDirectBodyState3D *BulletPhysicsServer3D::body_get_direct_state(RID p_bod return BulletPhysicsDirectBodyState3D::get_singleton(body); } -bool BulletPhysicsServer3D::body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, MotionResult *r_result, bool p_exclude_raycast_shapes) { +bool BulletPhysicsServer3D::body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, MotionResult *r_result, bool p_exclude_raycast_shapes, const Set<RID> &p_exclude) { RigidBodyBullet *body = rigid_body_owner.getornull(p_body); ERR_FAIL_COND_V(!body, false); ERR_FAIL_COND_V(!body->get_space(), false); - return body->get_space()->test_body_motion(body, p_from, p_motion, p_infinite_inertia, r_result, p_exclude_raycast_shapes); + return body->get_space()->test_body_motion(body, p_from, p_motion, p_infinite_inertia, r_result, p_exclude_raycast_shapes, p_exclude); } int BulletPhysicsServer3D::body_test_ray_separation(RID p_body, const Transform3D &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, SeparationResult *r_results, int p_result_max, real_t p_margin) { diff --git a/modules/bullet/bullet_physics_server.h b/modules/bullet/bullet_physics_server.h index d34d619ba2..7f0934e679 100644 --- a/modules/bullet/bullet_physics_server.h +++ b/modules/bullet/bullet_physics_server.h @@ -253,7 +253,7 @@ public: // this function only works on physics process, errors and returns null otherwise virtual PhysicsDirectBodyState3D *body_get_direct_state(RID p_body) override; - virtual bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, MotionResult *r_result = nullptr, bool p_exclude_raycast_shapes = true) override; + virtual bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, MotionResult *r_result = nullptr, bool p_exclude_raycast_shapes = true, const Set<RID> &p_exclude = Set<RID>()) override; virtual int body_test_ray_separation(RID p_body, const Transform3D &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, SeparationResult *r_results, int p_result_max, real_t p_margin = 0.001) override; /* SOFT BODY API */ diff --git a/modules/bullet/godot_result_callbacks.cpp b/modules/bullet/godot_result_callbacks.cpp index 399b102284..1f962772e7 100644 --- a/modules/bullet/godot_result_callbacks.cpp +++ b/modules/bullet/godot_result_callbacks.cpp @@ -135,6 +135,10 @@ bool GodotKinClosestConvexResultCallback::needsCollision(btBroadphaseProxy *prox if (m_self_object->has_collision_exception(gObj) || gObj->has_collision_exception(m_self_object)) { return false; } + + if (m_exclude->has(gObj->get_self())) { + return false; + } } return true; } else { diff --git a/modules/bullet/godot_result_callbacks.h b/modules/bullet/godot_result_callbacks.h index 9216322108..96a649d77a 100644 --- a/modules/bullet/godot_result_callbacks.h +++ b/modules/bullet/godot_result_callbacks.h @@ -100,11 +100,13 @@ public: struct GodotKinClosestConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback { public: const RigidBodyBullet *m_self_object; + const Set<RID> *m_exclude; const bool m_infinite_inertia; - GodotKinClosestConvexResultCallback(const btVector3 &convexFromWorld, const btVector3 &convexToWorld, const RigidBodyBullet *p_self_object, bool p_infinite_inertia) : + GodotKinClosestConvexResultCallback(const btVector3 &convexFromWorld, const btVector3 &convexToWorld, const RigidBodyBullet *p_self_object, bool p_infinite_inertia, const Set<RID> *p_exclude) : btCollisionWorld::ClosestConvexResultCallback(convexFromWorld, convexToWorld), m_self_object(p_self_object), + m_exclude(p_exclude), m_infinite_inertia(p_infinite_inertia) {} virtual bool needsCollision(btBroadphaseProxy *proxy0) const; diff --git a/modules/bullet/rigid_body_bullet.cpp b/modules/bullet/rigid_body_bullet.cpp index ce39d4f0df..0d2cd1f5a0 100644 --- a/modules/bullet/rigid_body_bullet.cpp +++ b/modules/bullet/rigid_body_bullet.cpp @@ -114,6 +114,16 @@ Transform3D BulletPhysicsDirectBodyState3D::get_transform() const { return body->get_transform(); } +Vector3 BulletPhysicsDirectBodyState3D::get_velocity_at_local_position(const Vector3 &p_position) const { + btVector3 local_position; + G_TO_B(p_position, local_position); + + Vector3 velocity; + B_TO_G(body->btBody->getVelocityInLocalPoint(local_position), velocity); + + return velocity; +} + void BulletPhysicsDirectBodyState3D::add_central_force(const Vector3 &p_force) { body->apply_central_force(p_force); } diff --git a/modules/bullet/rigid_body_bullet.h b/modules/bullet/rigid_body_bullet.h index 606df7134b..5e102d8b05 100644 --- a/modules/bullet/rigid_body_bullet.h +++ b/modules/bullet/rigid_body_bullet.h @@ -110,6 +110,8 @@ public: virtual void set_transform(const Transform3D &p_transform) override; virtual Transform3D get_transform() const override; + virtual Vector3 get_velocity_at_local_position(const Vector3 &p_position) const override; + virtual void add_central_force(const Vector3 &p_force) override; virtual void add_force(const Vector3 &p_force, const Vector3 &p_position = Vector3()) override; virtual void add_torque(const Vector3 &p_torque) override; diff --git a/modules/bullet/space_bullet.cpp b/modules/bullet/space_bullet.cpp index 583900e6bc..c6d835742d 100644 --- a/modules/bullet/space_bullet.cpp +++ b/modules/bullet/space_bullet.cpp @@ -908,7 +908,7 @@ static Ref<StandardMaterial3D> red_mat; static Ref<StandardMaterial3D> blue_mat; #endif -bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer3D::MotionResult *r_result, bool p_exclude_raycast_shapes) { +bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer3D::MotionResult *r_result, bool p_exclude_raycast_shapes, const Set<RID> &p_exclude) { #if debug_test_motion /// Yes I know this is not good, but I've used it as fast debugging hack. /// I'm leaving it here just for speedup the other eventual debugs @@ -948,7 +948,7 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform3D &p btVector3 initial_recover_motion(0, 0, 0); { /// Phase one - multi shapes depenetration using margin for (int t(RECOVERING_MOVEMENT_CYCLES); 0 < t; --t) { - if (!recover_from_penetration(p_body, body_transform, RECOVERING_MOVEMENT_SCALE, p_infinite_inertia, initial_recover_motion)) { + if (!recover_from_penetration(p_body, body_transform, RECOVERING_MOVEMENT_SCALE, p_infinite_inertia, initial_recover_motion, nullptr, p_exclude)) { break; } } @@ -1000,7 +1000,7 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform3D &p break; } - GodotKinClosestConvexResultCallback btResult(shape_world_from.getOrigin(), shape_world_to.getOrigin(), p_body, p_infinite_inertia); + GodotKinClosestConvexResultCallback btResult(shape_world_from.getOrigin(), shape_world_to.getOrigin(), p_body, p_infinite_inertia, &p_exclude); btResult.m_collisionFilterGroup = p_body->get_collision_layer(); btResult.m_collisionFilterMask = p_body->get_collision_mask(); @@ -1023,7 +1023,7 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform3D &p btVector3 __rec(0, 0, 0); RecoverResult r_recover_result; - has_penetration = recover_from_penetration(p_body, body_transform, 1, p_infinite_inertia, __rec, &r_recover_result); + has_penetration = recover_from_penetration(p_body, body_transform, 1, p_infinite_inertia, __rec, &r_recover_result, p_exclude); // Parse results if (r_result) { @@ -1173,7 +1173,7 @@ public: } }; -bool SpaceBullet::recover_from_penetration(RigidBodyBullet *p_body, const btTransform &p_body_position, btScalar p_recover_movement_scale, bool p_infinite_inertia, btVector3 &r_delta_recover_movement, RecoverResult *r_recover_result) { +bool SpaceBullet::recover_from_penetration(RigidBodyBullet *p_body, const btTransform &p_body_position, btScalar p_recover_movement_scale, bool p_infinite_inertia, btVector3 &r_delta_recover_movement, RecoverResult *r_recover_result, const Set<RID> &p_exclude) { // Calculate the cumulative AABB of all shapes of the kinematic body btVector3 aabb_min, aabb_max; bool shapes_found = false; @@ -1242,6 +1242,12 @@ bool SpaceBullet::recover_from_penetration(RigidBodyBullet *p_body, const btTran for (int i = recover_broad_result.results.size() - 1; 0 <= i; --i) { btCollisionObject *otherObject = recover_broad_result.results[i].collision_object; + + CollisionObjectBullet *gObj = static_cast<CollisionObjectBullet *>(otherObject->getUserPointer()); + if (p_exclude.has(gObj->get_self())) { + continue; + } + if (p_infinite_inertia && !otherObject->isStaticOrKinematicObject()) { otherObject->activate(); // Force activation of hitten rigid, soft body continue; diff --git a/modules/bullet/space_bullet.h b/modules/bullet/space_bullet.h index 36d0538e6b..2070e0e633 100644 --- a/modules/bullet/space_bullet.h +++ b/modules/bullet/space_bullet.h @@ -188,7 +188,7 @@ public: real_t get_linear_damp() const { return linear_damp; } real_t get_angular_damp() const { return angular_damp; } - bool test_body_motion(RigidBodyBullet *p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer3D::MotionResult *r_result, bool p_exclude_raycast_shapes); + bool test_body_motion(RigidBodyBullet *p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer3D::MotionResult *r_result, bool p_exclude_raycast_shapes, const Set<RID> &p_exclude = Set<RID>()); int test_ray_separation(RigidBodyBullet *p_body, const Transform3D &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, PhysicsServer3D::SeparationResult *r_results, int p_result_max, real_t p_margin); private: @@ -209,7 +209,7 @@ private: RecoverResult() {} }; - bool recover_from_penetration(RigidBodyBullet *p_body, const btTransform &p_body_position, btScalar p_recover_movement_scale, bool p_infinite_inertia, btVector3 &r_delta_recover_movement, RecoverResult *r_recover_result = nullptr); + bool recover_from_penetration(RigidBodyBullet *p_body, const btTransform &p_body_position, btScalar p_recover_movement_scale, bool p_infinite_inertia, btVector3 &r_delta_recover_movement, RecoverResult *r_recover_result = nullptr, const Set<RID> &p_exclude = Set<RID>()); /// This is an API that recover a kinematic object from penetration /// This allow only Convex Convex test and it always use GJK algorithm, With this API we don't benefit of Bullet special accelerated functions bool RFP_convex_convex_test(const btConvexShape *p_shapeA, const btConvexShape *p_shapeB, btCollisionObject *p_objectB, int p_shapeId_A, int p_shapeId_B, const btTransform &p_transformA, const btTransform &p_transformB, btScalar p_recover_movement_scale, btVector3 &r_delta_recover_movement, RecoverResult *r_recover_result = nullptr); diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index b47fa35f1a..91465bcfea 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -29,8 +29,8 @@ /*************************************************************************/ #include "csg_shape.h" + #include "core/math/geometry_2d.h" -#include "scene/3d/path_3d.h" void CSGShape3D::set_use_collision(bool p_enable) { if (use_collision == p_enable) { @@ -1676,109 +1676,80 @@ CSGTorus3D::CSGTorus3D() { /////////////// CSGBrush *CSGPolygon3D::_build_brush() { - // set our bounding box + CSGBrush *brush = memnew(CSGBrush); if (polygon.size() < 3) { - return memnew(CSGBrush); + return brush; } - Vector<Point2> final_polygon = polygon; - - if (Triangulate::get_area(final_polygon) > 0) { - final_polygon.reverse(); + // Triangulate polygon shape. + Vector<Point2> shape_polygon = polygon; + if (Triangulate::get_area(shape_polygon) > 0) { + shape_polygon.reverse(); } - - Vector<int> triangles = Geometry2D::triangulate_polygon(final_polygon); - - if (triangles.size() < 3) { - return memnew(CSGBrush); + int shape_sides = shape_polygon.size(); + Vector<int> shape_faces = Geometry2D::triangulate_polygon(shape_polygon); + ERR_FAIL_COND_V_MSG(shape_faces.size() < 3, brush, "Failed to triangulate CSGPolygon"); + + // Get polygon enclosing Rect2. + Rect2 shape_rect(shape_polygon[0], Vector2()); + for (int i = 1; i < shape_sides; i++) { + shape_rect.expand_to(shape_polygon[i]); } - Path3D *path = nullptr; + // If MODE_PATH, check if curve has changed. Ref<Curve3D> curve; - - // get bounds for our polygon - Vector2 final_polygon_min; - Vector2 final_polygon_max; - for (int i = 0; i < final_polygon.size(); i++) { - Vector2 p = final_polygon[i]; - if (i == 0) { - final_polygon_min = p; - final_polygon_max = final_polygon_min; - } else { - if (p.x < final_polygon_min.x) { - final_polygon_min.x = p.x; - } - if (p.y < final_polygon_min.y) { - final_polygon_min.y = p.y; - } - - if (p.x > final_polygon_max.x) { - final_polygon_max.x = p.x; + if (mode == MODE_PATH) { + Path3D *current_path = Object::cast_to<Path3D>(get_node_or_null(path_node)); + if (path != current_path) { + if (path) { + path->disconnect("tree_exited", callable_mp(this, &CSGPolygon3D::_path_exited)); + path->disconnect("curve_changed", callable_mp(this, &CSGPolygon3D::_path_changed)); } - if (p.y > final_polygon_max.y) { - final_polygon_max.y = p.y; + path = current_path; + if (path) { + path->connect("tree_exited", callable_mp(this, &CSGPolygon3D::_path_exited)); + path->connect("curve_changed", callable_mp(this, &CSGPolygon3D::_path_changed)); } } - } - Vector2 final_polygon_size = final_polygon_max - final_polygon_min; - if (mode == MODE_PATH) { - if (!has_node(path_node)) { - return memnew(CSGBrush); - } - Node *n = get_node(path_node); - if (!n) { - return memnew(CSGBrush); - } - path = Object::cast_to<Path3D>(n); if (!path) { - return memnew(CSGBrush); + return brush; } - if (path != path_cache) { - if (path_cache) { - path_cache->disconnect("tree_exited", callable_mp(this, &CSGPolygon3D::_path_exited)); - path_cache->disconnect("curve_changed", callable_mp(this, &CSGPolygon3D::_path_changed)); - path_cache = nullptr; - } - - path_cache = path; - - path_cache->connect("tree_exited", callable_mp(this, &CSGPolygon3D::_path_exited)); - path_cache->connect("curve_changed", callable_mp(this, &CSGPolygon3D::_path_changed)); - } curve = path->get_curve(); - if (curve.is_null()) { - return memnew(CSGBrush); - } - if (curve->get_baked_length() <= 0) { - return memnew(CSGBrush); + if (curve.is_null() || curve->get_point_count() < 2) { + return brush; } } - CSGBrush *brush = memnew(CSGBrush); - - int face_count = 0; + // Calculate the number extrusions, ends and faces. + int extrusions = 0; + int extrusion_face_count = shape_sides * 2; + int end_count = 0; + int shape_face_count = shape_faces.size() / 3; switch (mode) { case MODE_DEPTH: - face_count = triangles.size() * 2 / 3 + (final_polygon.size()) * 2; + extrusions = 1; + end_count = 2; break; case MODE_SPIN: - face_count = (spin_degrees < 360 ? triangles.size() * 2 / 3 : 0) + (final_polygon.size()) * 2 * spin_sides; + extrusions = spin_sides; + if (spin_degrees < 360) { + end_count = 2; + } break; case MODE_PATH: { - float bl = curve->get_baked_length(); - int splits = MAX(2, Math::ceil(bl / path_interval)); - if (path_joined) { - face_count = splits * final_polygon.size() * 2; - } else { - face_count = triangles.size() * 2 / 3 + splits * final_polygon.size() * 2; + extrusions = Math::ceil(1.0 * curve->get_point_count() / path_interval); + if (!path_joined) { + end_count = 2; + extrusions -= 1; } } break; } + int face_count = extrusions * extrusion_face_count + end_count * shape_face_count; - bool invert_val = is_inverting_faces(); + // Intialize variables used to create the mesh. Ref<Material> material = get_material(); Vector<Vector3> faces; @@ -1789,362 +1760,216 @@ CSGBrush *CSGPolygon3D::_build_brush() { faces.resize(face_count * 3); uvs.resize(face_count * 3); - smooth.resize(face_count); materials.resize(face_count); invert.resize(face_count); - AABB aabb; //must be computed - { - Vector3 *facesw = faces.ptrw(); - Vector2 *uvsw = uvs.ptrw(); - bool *smoothw = smooth.ptrw(); - Ref<Material> *materialsw = materials.ptrw(); - bool *invertw = invert.ptrw(); - - int face = 0; - - switch (mode) { - case MODE_DEPTH: { - //add triangles, front and back - for (int i = 0; i < 2; i++) { - for (int j = 0; j < triangles.size(); j += 3) { - for (int k = 0; k < 3; k++) { - int src[3] = { 0, i == 0 ? 1 : 2, i == 0 ? 2 : 1 }; - Vector2 p = final_polygon[triangles[j + src[k]]]; - Vector3 v = Vector3(p.x, p.y, 0); - if (i == 0) { - v.z -= depth; - } - facesw[face * 3 + k] = v; - uvsw[face * 3 + k] = (p - final_polygon_min) / final_polygon_size; - if (i == 0) { - uvsw[face * 3 + k].x = 1.0 - uvsw[face * 3 + k].x; /* flip x */ - } - } - - smoothw[face] = false; - materialsw[face] = material; - invertw[face] = invert_val; - face++; - } - } - - //add triangles for depth - for (int i = 0; i < final_polygon.size(); i++) { - int i_n = (i + 1) % final_polygon.size(); - - Vector3 v[4] = { - Vector3(final_polygon[i].x, final_polygon[i].y, -depth), - Vector3(final_polygon[i_n].x, final_polygon[i_n].y, -depth), - Vector3(final_polygon[i_n].x, final_polygon[i_n].y, 0), - Vector3(final_polygon[i].x, final_polygon[i].y, 0), - }; - - Vector2 u[4] = { - Vector2(0, 0), - Vector2(0, 1), - Vector2(1, 1), - Vector2(1, 0) - }; + Vector3 *facesw = faces.ptrw(); + Vector2 *uvsw = uvs.ptrw(); + bool *smoothw = smooth.ptrw(); + Ref<Material> *materialsw = materials.ptrw(); + bool *invertw = invert.ptrw(); + + int face = 0; + Transform3D base_xform; + Transform3D current_xform; + Transform3D previous_xform; + double u_step = 1.0 / extrusions; + double v_step = 1.0 / shape_sides; + double spin_step = Math::deg2rad(spin_degrees / spin_sides); + double extrusion_step = 1.0 / extrusions; + if (mode == MODE_PATH) { + if (path_joined) { + extrusion_step = 1.0 / (extrusions - 1); + } + extrusion_step *= curve->get_baked_length(); + } - // face 1 - facesw[face * 3 + 0] = v[0]; - facesw[face * 3 + 1] = v[1]; - facesw[face * 3 + 2] = v[2]; + if (mode == MODE_PATH) { + if (!path_local) { + base_xform = path->get_global_transform(); + } - uvsw[face * 3 + 0] = u[0]; - uvsw[face * 3 + 1] = u[1]; - uvsw[face * 3 + 2] = u[2]; + Vector3 current_point = curve->interpolate_baked(0); + Vector3 next_point = curve->interpolate_baked(extrusion_step); + Vector3 current_up = Vector3(0, 1, 0); + Vector3 direction = next_point - current_point; - smoothw[face] = smooth_faces; - invertw[face] = invert_val; - materialsw[face] = material; + if (path_joined) { + Vector3 last_point = curve->interpolate_baked(curve->get_baked_length()); + direction = next_point - last_point; + } - face++; + switch (path_rotation) { + case PATH_ROTATION_POLYGON: + direction = Vector3(0, 0, -1); + break; + case PATH_ROTATION_PATH: + break; + case PATH_ROTATION_PATH_FOLLOW: + current_up = curve->interpolate_baked_up_vector(0); + break; + } - // face 2 - facesw[face * 3 + 0] = v[2]; - facesw[face * 3 + 1] = v[3]; - facesw[face * 3 + 2] = v[0]; + Transform3D facing = Transform3D().looking_at(direction, current_up); + current_xform = base_xform.translated(current_point) * facing; + } - uvsw[face * 3 + 0] = u[2]; - uvsw[face * 3 + 1] = u[3]; - uvsw[face * 3 + 2] = u[0]; + // Create the mesh. + if (end_count > 0) { + // Add front end face. + for (int face_idx = 0; face_idx < shape_face_count; face_idx++) { + for (int face_vertex_idx = 0; face_vertex_idx < 3; face_vertex_idx++) { + // We need to reverse the rotation of the shape face vertices. + int index = shape_faces[face_idx * 3 + 2 - face_vertex_idx]; + Point2 p = shape_polygon[index]; + Point2 uv = (p - shape_rect.position) / shape_rect.size; + + // Use the left side of the bottom half of the y-inverted texture. + uv.x = uv.x / 2; + uv.y = 1 - (uv.y / 2); + + facesw[face * 3 + face_vertex_idx] = current_xform.xform(Vector3(p.x, p.y, 0)); + uvsw[face * 3 + face_vertex_idx] = uv; + } - smoothw[face] = smooth_faces; - invertw[face] = invert_val; - materialsw[face] = material; + smoothw[face] = false; + materialsw[face] = material; + invertw[face] = invert_faces; + face++; + } + } - face++; - } + // Add extrusion faces. + for (int x0 = 0; x0 < extrusions; x0++) { + previous_xform = current_xform; + switch (mode) { + case MODE_DEPTH: { + current_xform.translate(Vector3(0, 0, -depth)); } break; case MODE_SPIN: { - for (int i = 0; i < spin_sides; i++) { - float inci = float(i) / spin_sides; - float inci_n = float((i + 1)) / spin_sides; - - float angi = -Math::deg2rad(inci * spin_degrees); - float angi_n = -Math::deg2rad(inci_n * spin_degrees); - - Vector3 normali = Vector3(Math::cos(angi), 0, Math::sin(angi)); - Vector3 normali_n = Vector3(Math::cos(angi_n), 0, Math::sin(angi_n)); - - //add triangles for depth - for (int j = 0; j < final_polygon.size(); j++) { - int j_n = (j + 1) % final_polygon.size(); - - Vector3 v[4] = { - Vector3(normali.x * final_polygon[j].x, final_polygon[j].y, normali.z * final_polygon[j].x), - Vector3(normali.x * final_polygon[j_n].x, final_polygon[j_n].y, normali.z * final_polygon[j_n].x), - Vector3(normali_n.x * final_polygon[j_n].x, final_polygon[j_n].y, normali_n.z * final_polygon[j_n].x), - Vector3(normali_n.x * final_polygon[j].x, final_polygon[j].y, normali_n.z * final_polygon[j].x), - }; - - Vector2 u[4] = { - Vector2(0, 0), - Vector2(0, 1), - Vector2(1, 1), - Vector2(1, 0) - }; - - // face 1 - facesw[face * 3 + 0] = v[0]; - facesw[face * 3 + 1] = v[2]; - facesw[face * 3 + 2] = v[1]; - - uvsw[face * 3 + 0] = u[0]; - uvsw[face * 3 + 1] = u[2]; - uvsw[face * 3 + 2] = u[1]; - - smoothw[face] = smooth_faces; - invertw[face] = invert_val; - materialsw[face] = material; - - face++; - - // face 2 - facesw[face * 3 + 0] = v[2]; - facesw[face * 3 + 1] = v[0]; - facesw[face * 3 + 2] = v[3]; - - uvsw[face * 3 + 0] = u[2]; - uvsw[face * 3 + 1] = u[0]; - uvsw[face * 3 + 2] = u[3]; - - smoothw[face] = smooth_faces; - invertw[face] = invert_val; - materialsw[face] = material; - - face++; - } - - if (i == 0 && spin_degrees < 360) { - for (int j = 0; j < triangles.size(); j += 3) { - for (int k = 0; k < 3; k++) { - int src[3] = { 0, 2, 1 }; - Vector2 p = final_polygon[triangles[j + src[k]]]; - Vector3 v = Vector3(p.x, p.y, 0); - facesw[face * 3 + k] = v; - uvsw[face * 3 + k] = (p - final_polygon_min) / final_polygon_size; - } - - smoothw[face] = false; - materialsw[face] = material; - invertw[face] = invert_val; - face++; - } - } - - if (i == spin_sides - 1 && spin_degrees < 360) { - for (int j = 0; j < triangles.size(); j += 3) { - for (int k = 0; k < 3; k++) { - int src[3] = { 0, 1, 2 }; - Vector2 p = final_polygon[triangles[j + src[k]]]; - Vector3 v = Vector3(normali_n.x * p.x, p.y, normali_n.z * p.x); - facesw[face * 3 + k] = v; - uvsw[face * 3 + k] = (p - final_polygon_min) / final_polygon_size; - uvsw[face * 3 + k].x = 1.0 - uvsw[face * 3 + k].x; /* flip x */ - } - - smoothw[face] = false; - materialsw[face] = material; - invertw[face] = invert_val; - face++; - } - } - } + current_xform.rotate(Vector3(0, 1, 0), spin_step); } break; case MODE_PATH: { - float bl = curve->get_baked_length(); - int splits = MAX(2, Math::ceil(bl / path_interval)); - float u1 = 0.0; - float u2 = path_continuous_u ? 0.0 : 1.0; - - Transform3D path_to_this; - if (!path_local) { - // center on paths origin - path_to_this = get_global_transform().affine_inverse() * path->get_global_transform(); - } - - Transform3D prev_xf; - - Vector3 lookat_dir; - - if (path_rotation == PATH_ROTATION_POLYGON) { - lookat_dir = (path->get_global_transform().affine_inverse() * get_global_transform()).xform(Vector3(0, 0, -1)); - } else { - Vector3 p1, p2; - p1 = curve->interpolate_baked(0); - p2 = curve->interpolate_baked(0.1); - lookat_dir = (p2 - p1).normalized(); - } - - for (int i = 0; i <= splits; i++) { - float ofs = i * path_interval; - if (ofs > bl) { - ofs = bl; - } - if (i == splits && path_joined) { - ofs = 0.0; - } - - Transform3D xf; - xf.origin = curve->interpolate_baked(ofs); - - Vector3 local_dir; - - if (path_rotation == PATH_ROTATION_PATH_FOLLOW && ofs > 0) { - //before end - Vector3 p1 = curve->interpolate_baked(ofs - 0.1); - Vector3 p2 = curve->interpolate_baked(ofs); - local_dir = (p2 - p1).normalized(); - + double previous_offset = x0 * extrusion_step; + double current_offset = (x0 + 1) * extrusion_step; + double next_offset = (x0 + 2) * extrusion_step; + if (x0 == extrusions - 1) { + if (path_joined) { + current_offset = 0; + next_offset = extrusion_step; } else { - local_dir = lookat_dir; - } - - xf = xf.looking_at(xf.origin + local_dir, Vector3(0, 1, 0)); - Basis rot(Vector3(0, 0, 1), curve->interpolate_baked_tilt(ofs)); - - xf = xf * rot; //post mult - - xf = path_to_this * xf; - - if (i > 0) { - if (path_continuous_u) { - u1 = u2; - u2 += (prev_xf.origin - xf.origin).length(); - }; - - //put triangles where they belong - //add triangles for depth - for (int j = 0; j < final_polygon.size(); j++) { - int j_n = (j + 1) % final_polygon.size(); - - Vector3 v[4] = { - prev_xf.xform(Vector3(final_polygon[j].x, final_polygon[j].y, 0)), - prev_xf.xform(Vector3(final_polygon[j_n].x, final_polygon[j_n].y, 0)), - xf.xform(Vector3(final_polygon[j_n].x, final_polygon[j_n].y, 0)), - xf.xform(Vector3(final_polygon[j].x, final_polygon[j].y, 0)), - }; - - Vector2 u[4] = { - Vector2(u1, 1), - Vector2(u1, 0), - Vector2(u2, 0), - Vector2(u2, 1) - }; - - // face 1 - facesw[face * 3 + 0] = v[0]; - facesw[face * 3 + 1] = v[1]; - facesw[face * 3 + 2] = v[2]; - - uvsw[face * 3 + 0] = u[0]; - uvsw[face * 3 + 1] = u[1]; - uvsw[face * 3 + 2] = u[2]; - - smoothw[face] = smooth_faces; - invertw[face] = invert_val; - materialsw[face] = material; - - face++; - - // face 2 - facesw[face * 3 + 0] = v[2]; - facesw[face * 3 + 1] = v[3]; - facesw[face * 3 + 2] = v[0]; - - uvsw[face * 3 + 0] = u[2]; - uvsw[face * 3 + 1] = u[3]; - uvsw[face * 3 + 2] = u[0]; - - smoothw[face] = smooth_faces; - invertw[face] = invert_val; - materialsw[face] = material; - - face++; - } - } - - if (i == 0 && !path_joined) { - for (int j = 0; j < triangles.size(); j += 3) { - for (int k = 0; k < 3; k++) { - int src[3] = { 0, 1, 2 }; - Vector2 p = final_polygon[triangles[j + src[k]]]; - Vector3 v = Vector3(p.x, p.y, 0); - facesw[face * 3 + k] = xf.xform(v); - uvsw[face * 3 + k] = (p - final_polygon_min) / final_polygon_size; - } - - smoothw[face] = false; - materialsw[face] = material; - invertw[face] = invert_val; - face++; - } + next_offset = current_offset; } + } - if (i == splits && !path_joined) { - for (int j = 0; j < triangles.size(); j += 3) { - for (int k = 0; k < 3; k++) { - int src[3] = { 0, 2, 1 }; - Vector2 p = final_polygon[triangles[j + src[k]]]; - Vector3 v = Vector3(p.x, p.y, 0); - facesw[face * 3 + k] = xf.xform(v); - uvsw[face * 3 + k] = (p - final_polygon_min) / final_polygon_size; - uvsw[face * 3 + k].x = 1.0 - uvsw[face * 3 + k].x; /* flip x */ - } - - smoothw[face] = false; - materialsw[face] = material; - invertw[face] = invert_val; - face++; - } - } + Vector3 previous_point = curve->interpolate_baked(previous_offset); + Vector3 current_point = curve->interpolate_baked(current_offset); + Vector3 next_point = curve->interpolate_baked(next_offset); + Vector3 current_up = Vector3(0, 1, 0); + Vector3 direction = next_point - previous_point; - prev_xf = xf; + switch (path_rotation) { + case PATH_ROTATION_POLYGON: + direction = Vector3(0, 0, -1); + break; + case PATH_ROTATION_PATH: + break; + case PATH_ROTATION_PATH_FOLLOW: + current_up = curve->interpolate_baked_up_vector(current_offset); + break; } + Transform3D facing = Transform3D().looking_at(direction, current_up); + current_xform = base_xform.translated(current_point) * facing; } break; } - if (face != face_count) { - ERR_PRINT("Face mismatch bug! fix code"); + double u0 = x0 * u_step; + double u1 = ((x0 + 1) * u_step); + if (mode == MODE_PATH && !path_continuous_u) { + u0 = 0.0; + u1 = 1.0; } - for (int i = 0; i < face_count * 3; i++) { - if (i == 0) { - aabb.position = facesw[i]; - } else { - aabb.expand_to(facesw[i]); + + for (int y0 = 0; y0 < shape_sides; y0++) { + int y1 = (y0 + 1) % shape_sides; + // Use the top half of the texture. + double v0 = (y0 * v_step) / 2; + double v1 = ((y0 + 1) * v_step) / 2; + + Vector3 v[4] = { + previous_xform.xform(Vector3(shape_polygon[y0].x, shape_polygon[y0].y, 0)), + current_xform.xform(Vector3(shape_polygon[y0].x, shape_polygon[y0].y, 0)), + current_xform.xform(Vector3(shape_polygon[y1].x, shape_polygon[y1].y, 0)), + previous_xform.xform(Vector3(shape_polygon[y1].x, shape_polygon[y1].y, 0)), + }; + + Vector2 u[4] = { + Vector2(u0, v0), + Vector2(u1, v0), + Vector2(u1, v1), + Vector2(u0, v1), + }; + + // Face 1 + facesw[face * 3 + 0] = v[0]; + facesw[face * 3 + 1] = v[1]; + facesw[face * 3 + 2] = v[2]; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[1]; + uvsw[face * 3 + 2] = u[2]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_faces; + materialsw[face] = material; + + face++; + + // Face 2 + facesw[face * 3 + 0] = v[2]; + facesw[face * 3 + 1] = v[3]; + facesw[face * 3 + 2] = v[0]; + + uvsw[face * 3 + 0] = u[2]; + uvsw[face * 3 + 1] = u[3]; + uvsw[face * 3 + 2] = u[0]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_faces; + materialsw[face] = material; + + face++; + } + } + + if (end_count > 1) { + // Add back end face. + for (int face_idx = 0; face_idx < shape_face_count; face_idx++) { + for (int face_vertex_idx = 0; face_vertex_idx < 3; face_vertex_idx++) { + int index = shape_faces[face_idx * 3 + face_vertex_idx]; + Point2 p = shape_polygon[index]; + Point2 uv = (p - shape_rect.position) / shape_rect.size; + + // Use the x-inverted ride side of the bottom half of the y-inverted texture. + uv.x = 1 - uv.x / 2; + uv.y = 1 - (uv.y / 2); + + facesw[face * 3 + face_vertex_idx] = current_xform.xform(Vector3(p.x, p.y, 0)); + uvsw[face * 3 + face_vertex_idx] = uv; } - // invert UVs on the Y-axis OpenGL = upside down - uvsw[i].y = 1.0 - uvsw[i].y; + smoothw[face] = false; + materialsw[face] = material; + invertw[face] = invert_faces; + face++; } } + ERR_FAIL_COND_V_MSG(face != face_count, brush, "Bug: Failed to create the CSGPolygon mesh correctly."); + brush->build_from_faces(faces, uvs, smooth, materials, invert); return brush; @@ -2152,10 +1977,10 @@ CSGBrush *CSGPolygon3D::_build_brush() { void CSGPolygon3D::_notification(int p_what) { if (p_what == NOTIFICATION_EXIT_TREE) { - if (path_cache) { - path_cache->disconnect("tree_exited", callable_mp(this, &CSGPolygon3D::_path_exited)); - path_cache->disconnect("curve_changed", callable_mp(this, &CSGPolygon3D::_path_changed)); - path_cache = nullptr; + if (path) { + path->disconnect("tree_exited", callable_mp(this, &CSGPolygon3D::_path_exited)); + path->disconnect("curve_changed", callable_mp(this, &CSGPolygon3D::_path_changed)); + path = nullptr; } } } @@ -2180,7 +2005,7 @@ void CSGPolygon3D::_path_changed() { } void CSGPolygon3D::_path_exited() { - path_cache = nullptr; + path = nullptr; } void CSGPolygon3D::_bind_methods() { @@ -2202,10 +2027,10 @@ void CSGPolygon3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_path_node", "path"), &CSGPolygon3D::set_path_node); ClassDB::bind_method(D_METHOD("get_path_node"), &CSGPolygon3D::get_path_node); - ClassDB::bind_method(D_METHOD("set_path_interval", "distance"), &CSGPolygon3D::set_path_interval); + ClassDB::bind_method(D_METHOD("set_path_interval", "interval"), &CSGPolygon3D::set_path_interval); ClassDB::bind_method(D_METHOD("get_path_interval"), &CSGPolygon3D::get_path_interval); - ClassDB::bind_method(D_METHOD("set_path_rotation", "mode"), &CSGPolygon3D::set_path_rotation); + ClassDB::bind_method(D_METHOD("set_path_rotation", "path_rotation"), &CSGPolygon3D::set_path_rotation); ClassDB::bind_method(D_METHOD("get_path_rotation"), &CSGPolygon3D::get_path_rotation); ClassDB::bind_method(D_METHOD("set_path_local", "enable"), &CSGPolygon3D::set_path_local); @@ -2228,11 +2053,11 @@ void CSGPolygon3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Depth,Spin,Path"), "set_mode", "get_mode"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp"), "set_depth", "get_depth"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth", PROPERTY_HINT_RANGE, "0.01,100.0,0.01,or_greater,exp"), "set_depth", "get_depth"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "spin_degrees", PROPERTY_HINT_RANGE, "1,360,0.1"), "set_spin_degrees", "get_spin_degrees"); ADD_PROPERTY(PropertyInfo(Variant::INT, "spin_sides", PROPERTY_HINT_RANGE, "3,64,1"), "set_spin_sides", "get_spin_sides"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "path_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Path3D"), "set_path_node", "get_path_node"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_interval", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp"), "set_path_interval", "get_path_interval"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_interval", PROPERTY_HINT_RANGE, "0.1,1.0,0.05,exp"), "set_path_interval", "get_path_interval"); ADD_PROPERTY(PropertyInfo(Variant::INT, "path_rotation", PROPERTY_HINT_ENUM, "Polygon,Path,PathFollow"), "set_path_rotation", "get_path_rotation"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_local"), "set_path_local", "is_path_local"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_continuous_u"), "set_path_continuous_u", "is_path_continuous_u"); @@ -2301,7 +2126,7 @@ float CSGPolygon3D::get_spin_degrees() const { return spin_degrees; } -void CSGPolygon3D::set_spin_sides(const int p_spin_sides) { +void CSGPolygon3D::set_spin_sides(int p_spin_sides) { ERR_FAIL_COND(p_spin_sides < 3); spin_sides = p_spin_sides; _make_dirty(); @@ -2323,7 +2148,7 @@ NodePath CSGPolygon3D::get_path_node() const { } void CSGPolygon3D::set_path_interval(float p_interval) { - ERR_FAIL_COND_MSG(p_interval < 0.001, "Path interval cannot be smaller than 0.001."); + ERR_FAIL_COND_MSG(p_interval <= 0 || p_interval > 1, "Path interval must be greater than 0 and less than or equal to 1.0."); path_interval = p_interval; _make_dirty(); update_gizmos(); @@ -2400,10 +2225,10 @@ CSGPolygon3D::CSGPolygon3D() { spin_degrees = 360; spin_sides = 8; smooth_faces = false; - path_interval = 1; - path_rotation = PATH_ROTATION_PATH; + path_interval = 1.0; + path_rotation = PATH_ROTATION_PATH_FOLLOW; path_local = false; - path_continuous_u = false; + path_continuous_u = true; path_joined = false; - path_cache = nullptr; + path = nullptr; } diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index de7de09f00..5814ce4c83 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -34,6 +34,7 @@ #define CSGJS_HEADER_ONLY #include "csg.h" +#include "scene/3d/path_3d.h" #include "scene/3d/visual_instance_3d.h" #include "scene/resources/concave_polygon_shape_3d.h" #include "thirdparty/misc/mikktspace.h" @@ -83,14 +84,14 @@ private: Vector<Vector3> vertices; Vector<Vector3> normals; Vector<Vector2> uvs; - Vector<float> tans; + Vector<real_t> tans; Ref<Material> material; int last_added = 0; Vector3 *verticesw = nullptr; Vector3 *normalsw = nullptr; Vector2 *uvsw = nullptr; - float *tansw = nullptr; + real_t *tansw = nullptr; }; //mikktspace callbacks @@ -168,10 +169,8 @@ public: class CSGPrimitive3D : public CSGShape3D { GDCLASS(CSGPrimitive3D, CSGShape3D); -private: - bool invert_faces; - protected: + bool invert_faces; CSGBrush *_create_brush_from_arrays(const Vector<Vector3> &p_vertices, const Vector<Vector2> &p_uv, const Vector<bool> &p_smooth, const Vector<Ref<Material>> &p_materials); static void _bind_methods(); @@ -361,7 +360,7 @@ private: PathRotation path_rotation; bool path_local; - Node *path_cache; + Path3D *path; bool smooth_faces; bool path_continuous_u; diff --git a/modules/csg/doc_classes/CSGPolygon3D.xml b/modules/csg/doc_classes/CSGPolygon3D.xml index 4f29786779..5309cde956 100644 --- a/modules/csg/doc_classes/CSGPolygon3D.xml +++ b/modules/csg/doc_classes/CSGPolygon3D.xml @@ -4,7 +4,7 @@ Extrudes a 2D polygon shape to create a 3D mesh. </brief_description> <description> - This node takes a 2D polygon shape and extrudes it to create a 3D mesh. + An array of 2D points is extruded to quickly and easily create a variety of 3D meshes. </description> <tutorials> </tutorials> @@ -12,63 +12,65 @@ </methods> <members> <member name="depth" type="float" setter="set_depth" getter="get_depth" default="1.0"> - Extrusion depth when [member mode] is [constant MODE_DEPTH]. + When [member mode] is [constant MODE_DEPTH], the depth of the extrusion. </member> <member name="material" type="Material" setter="set_material" getter="get_material"> - Material to use for the resulting mesh. + Material to use for the resulting mesh. The UV maps the top half of the material to the extruded shape (U along the length of the extrusions and V around the outline of the [member polygon]), the bottom-left quarter to the front end face, and the bottom-right quarter to the back end face. </member> <member name="mode" type="int" setter="set_mode" getter="get_mode" enum="CSGPolygon3D.Mode" default="0"> - Extrusion mode. + The [member mode] used to extrude the [member polygon]. </member> <member name="path_continuous_u" type="bool" setter="set_path_continuous_u" getter="is_path_continuous_u"> - If [code]true[/code] the u component of our uv will continuously increase in unison with the distance traveled along our path when [member mode] is [constant MODE_PATH]. + When [member mode] is [constant MODE_PATH], by default, the top half of the [member material] is stretched along the entire length of the extruded shape. If [code]false[/code] the top half of the material is repeated every step of the extrusion. </member> <member name="path_interval" type="float" setter="set_path_interval" getter="get_path_interval"> - Interval at which a new extrusion slice is added along the path when [member mode] is [constant MODE_PATH]. + When [member mode] is [constant MODE_PATH], the path interval or ratio of path points to extrusions. </member> <member name="path_joined" type="bool" setter="set_path_joined" getter="is_path_joined"> - If [code]true[/code] the start and end of our path are joined together ensuring there is no seam when [member mode] is [constant MODE_PATH]. + When [member mode] is [constant MODE_PATH], if [code]true[/code] the ends of the path are joined, by adding an extrusion between the last and first points of the path. </member> <member name="path_local" type="bool" setter="set_path_local" getter="is_path_local"> - If [code]false[/code] we extrude centered on our path, if [code]true[/code] we extrude in relation to the position of our CSGPolygon3D when [member mode] is [constant MODE_PATH]. + When [member mode] is [constant MODE_PATH], if [code]true[/code] the [Transform3D] of the [CSGPolygon3D] is used as the starting point for the extrusions, not the [Transform3D] of the [member path_node]. </member> <member name="path_node" type="NodePath" setter="set_path_node" getter="get_path_node"> - The [Shape3D] object containing the path along which we extrude when [member mode] is [constant MODE_PATH]. + When [member mode] is [constant MODE_PATH], the location of the [Path3D] object used to extrude the [member polygon]. </member> <member name="path_rotation" type="int" setter="set_path_rotation" getter="get_path_rotation" enum="CSGPolygon3D.PathRotation"> - The method by which each slice is rotated along the path when [member mode] is [constant MODE_PATH]. + When [member mode] is [constant MODE_PATH], the [enum PathRotation] method used to rotate the [member polygon] as it is extruded. </member> <member name="polygon" type="PackedVector2Array" setter="set_polygon" getter="get_polygon" default="PackedVector2Array(0, 0, 0, 1, 1, 1, 1, 0)"> - Point array that defines the shape that we'll extrude. + The point array that defines the 2D polygon that is extruded. </member> <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces" default="false"> - Generates smooth normals so smooth shading is applied to our mesh. + If [code]true[/code], applies smooth shading to the extrusions. </member> <member name="spin_degrees" type="float" setter="set_spin_degrees" getter="get_spin_degrees"> - Degrees to rotate our extrusion for each slice when [member mode] is [constant MODE_SPIN]. + When [member mode] is [constant MODE_SPIN], the total number of degrees the [member polygon] is rotated when extruding. </member> <member name="spin_sides" type="int" setter="set_spin_sides" getter="get_spin_sides"> - Number of extrusion when [member mode] is [constant MODE_SPIN]. + When [member mode] is [constant MODE_SPIN], the number of extrusions made. </member> </members> <constants> <constant name="MODE_DEPTH" value="0" enum="Mode"> - Shape3D is extruded to [member depth]. + The [member polygon] shape is extruded along the negative Z axis. </constant> <constant name="MODE_SPIN" value="1" enum="Mode"> - Shape3D is extruded by rotating it around an axis. + The [member polygon] shape is extruded by rotating it around the Y axis. </constant> <constant name="MODE_PATH" value="2" enum="Mode"> - Shape3D is extruded along a path set by a [Shape3D] set in [member path_node]. + The [member polygon] shape is extruded along the [Path3D] specified in [member path_node]. </constant> <constant name="PATH_ROTATION_POLYGON" value="0" enum="PathRotation"> - Slice is not rotated. + The [member polygon] shape is not rotated. + [b]Note:[/b] Requires the path Z coordinates to continually decrease to ensure viable shapes. </constant> <constant name="PATH_ROTATION_PATH" value="1" enum="PathRotation"> - Slice is rotated around the up vector of the path. + The [member polygon] shape is rotated along the path, but it is not rotated around the path axis. + [b]Note:[/b] Requires the path Z coordinates to continually decrease to ensure viable shapes. </constant> <constant name="PATH_ROTATION_PATH_FOLLOW" value="2" enum="PathRotation"> - Slice is rotate to match the path exactly. + The [member polygon] shape follows the path and its rotations around the path axis. </constant> </constants> </class> diff --git a/modules/fbx/data/fbx_mesh_data.cpp b/modules/fbx/data/fbx_mesh_data.cpp index 8b4b1e08b3..dcea476275 100644 --- a/modules/fbx/data/fbx_mesh_data.cpp +++ b/modules/fbx/data/fbx_mesh_data.cpp @@ -433,7 +433,7 @@ void FBXMeshData::sanitize_vertex_weights(const ImportState &state) { { // Sort - real_t *weights_ptr = vm->weights.ptrw(); + float *weights_ptr = vm->weights.ptrw(); int *bones_ptr = vm->bones.ptrw(); for (int i = 0; i < vm->weights.size(); i += 1) { for (int x = i + 1; x < vm->weights.size(); x += 1) { @@ -449,7 +449,7 @@ void FBXMeshData::sanitize_vertex_weights(const ImportState &state) { // Resize vm->weights.resize(max_vertex_influence_count); vm->bones.resize(max_vertex_influence_count); - real_t *weights_ptr = vm->weights.ptrw(); + float *weights_ptr = vm->weights.ptrw(); int *bones_ptr = vm->bones.ptrw(); for (int i = initial_size; i < max_vertex_influence_count; i += 1) { weights_ptr[i] = 0.0; diff --git a/modules/fbx/data/fbx_mesh_data.h b/modules/fbx/data/fbx_mesh_data.h index 7486c5c59c..24db4a5469 100644 --- a/modules/fbx/data/fbx_mesh_data.h +++ b/modules/fbx/data/fbx_mesh_data.h @@ -64,7 +64,7 @@ struct SurfaceData { }; struct VertexWeightMapping { - Vector<real_t> weights; + Vector<float> weights; Vector<int> bones; // This extra vector is used because the bone id is computed in a second step. // TODO Get rid of this extra step is a good idea. diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp index f3a0e9603f..26d3aed702 100644 --- a/modules/gdnative/nativescript/nativescript.cpp +++ b/modules/gdnative/nativescript/nativescript.cpp @@ -114,9 +114,25 @@ void NativeScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) #endif bool NativeScript::inherits_script(const Ref<Script> &p_script) const { -#ifndef _MSC_VER -#warning inheritance needs to be implemented in NativeScript -#endif + Ref<NativeScript> ns = p_script; + if (ns.is_null()) { + return false; + } + + const NativeScriptDesc *other_s = ns->get_script_desc(); + if (!other_s) { + return false; + } + + const NativeScriptDesc *s = get_script_desc(); + + while (s) { + if (s == other_s) { + return true; + } + s = s->base_data; + } + return false; } diff --git a/modules/gdnative/pluginscript/pluginscript_script.cpp b/modules/gdnative/pluginscript/pluginscript_script.cpp index 7fc8178e34..5380858582 100644 --- a/modules/gdnative/pluginscript/pluginscript_script.cpp +++ b/modules/gdnative/pluginscript/pluginscript_script.cpp @@ -139,9 +139,20 @@ bool PluginScript::can_instantiate() const { } bool PluginScript::inherits_script(const Ref<Script> &p_script) const { -#ifndef _MSC_VER -#warning inheritance needs to be implemented in PluginScript -#endif + Ref<PluginScript> ps = p_script; + if (ps.is_null()) { + return false; + } + + const PluginScript *s = this; + + while (s) { + if (s == p_script.ptr()) { + return true; + } + s = Object::cast_to<PluginScript>(s->_ref_base_parent.ptr()); + } + return false; } diff --git a/modules/gdnative/text/text_server_gdnative.cpp b/modules/gdnative/text/text_server_gdnative.cpp index 81dd570bcb..3ed3f5449e 100644 --- a/modules/gdnative/text/text_server_gdnative.cpp +++ b/modules/gdnative/text/text_server_gdnative.cpp @@ -449,12 +449,12 @@ bool TextServerGDNative::shaped_text_add_string(RID p_shaped, const String &p_te return interface->shaped_text_add_string(data, (godot_rid *)&p_shaped, (const godot_string *)&p_text, (const godot_rid **)p_fonts.ptr(), p_size, (const godot_dictionary *)&p_opentype_features, (const godot_string *)&p_language); } -bool TextServerGDNative::shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, VAlign p_inline_align, int p_length) { +bool TextServerGDNative::shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlign p_inline_align, int p_length) { ERR_FAIL_COND_V(interface == nullptr, false); return interface->shaped_text_add_object(data, (godot_rid *)&p_shaped, (const godot_variant *)&p_key, (const godot_vector2 *)&p_size, (godot_int)p_inline_align, p_length); } -bool TextServerGDNative::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, VAlign p_inline_align) { +bool TextServerGDNative::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlign p_inline_align) { ERR_FAIL_COND_V(interface == nullptr, false); return interface->shaped_text_resize_object(data, (godot_rid *)&p_shaped, (const godot_variant *)&p_key, (const godot_vector2 *)&p_size, (godot_int)p_inline_align); } @@ -498,9 +498,9 @@ bool TextServerGDNative::shaped_text_update_justification_ops(RID p_shaped) { return interface->shaped_text_update_justification_ops(data, (godot_rid *)&p_shaped); } -void TextServerGDNative::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint8_t p_clip_flags) { +void TextServerGDNative::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint8_t p_trim_flags) { ERR_FAIL_COND(interface == nullptr); - interface->shaped_text_overrun_trim_to_width(data, (godot_rid *)&p_shaped_line, p_width, p_clip_flags); + interface->shaped_text_overrun_trim_to_width(data, (godot_rid *)&p_shaped_line, p_width, p_trim_flags); }; bool TextServerGDNative::shaped_text_is_ready(RID p_shaped) const { diff --git a/modules/gdnative/text/text_server_gdnative.h b/modules/gdnative/text/text_server_gdnative.h index 7a0725f3d9..e1b36f4264 100644 --- a/modules/gdnative/text/text_server_gdnative.h +++ b/modules/gdnative/text/text_server_gdnative.h @@ -154,8 +154,8 @@ public: virtual bool shaped_text_get_preserve_control(RID p_shaped) const override; virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "") override; - virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, VAlign p_inline_align = VALIGN_CENTER, int p_length = 1) override; - virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, VAlign p_inline_align = VALIGN_CENTER) override; + virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlign p_inline_align = INLINE_ALIGN_CENTER, int p_length = 1) override; + virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlign p_inline_align = INLINE_ALIGN_CENTER) override; virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override; virtual RID shaped_text_get_parent(RID p_shaped) const override; @@ -167,7 +167,7 @@ public: virtual bool shaped_text_update_breaks(RID p_shaped) override; virtual bool shaped_text_update_justification_ops(RID p_shaped) override; - virtual void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_clip_flags) override; + virtual void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_trim_flags) override; virtual bool shaped_text_is_ready(RID p_shaped) const override; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 7c9d08b782..fe827a5b72 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -107,7 +107,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D // Locate class by constructing the path to it and following that path GDScriptParser::ClassNode *class_type = p_datatype.class_type; if (class_type) { - if (class_type->fqcn.begins_with(main_script->path) || (!main_script->name.is_empty() && class_type->fqcn.begins_with(main_script->name))) { + if ((!main_script->path.is_empty() && class_type->fqcn.begins_with(main_script->path)) || (!main_script->name.is_empty() && class_type->fqcn.begins_with(main_script->name))) { // Local class. List<StringName> names; while (class_type->outer) { diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index a500dfd51a..a21167ad95 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -2480,7 +2480,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode switch (dictionary->style) { case DictionaryNode::LUA_TABLE: if (key != nullptr && key->type != Node::IDENTIFIER) { - push_error("Expected identifier as dictionary key."); + push_error("Expected identifier as LUA-style dictionary key."); + advance(); + break; } if (!match(GDScriptTokenizer::Token::EQUAL)) { if (match(GDScriptTokenizer::Token::COLON)) { diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index be44f66423..ff0579a11c 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -43,6 +43,7 @@ #include "gltf_texture.h" #include "core/crypto/crypto_core.h" +#include "core/error/error_macros.h" #include "core/io/dir_access.h" #include "core/io/file_access.h" #include "core/io/json.h" @@ -4469,9 +4470,9 @@ Error GLTFDocument::_parse_lights(Ref<GLTFState> state) { const Dictionary &spot = d["spot"]; light->inner_cone_angle = spot["innerConeAngle"]; light->outer_cone_angle = spot["outerConeAngle"]; - ERR_FAIL_COND_V_MSG(light->inner_cone_angle >= light->outer_cone_angle, ERR_PARSE_ERROR, "The inner angle must be smaller than the outer angle."); + ERR_CONTINUE_MSG(light->inner_cone_angle >= light->outer_cone_angle, "The inner angle must be smaller than the outer angle."); } else if (type != "point" && type != "directional") { - ERR_FAIL_V_MSG(ERR_PARSE_ERROR, "Light type is unknown."); + ERR_CONTINUE_MSG(ERR_PARSE_ERROR, "Light type is unknown."); } state->lights.push_back(light); @@ -5380,15 +5381,16 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> state, Node *scene_parent // and attach it to the bone_attachment scene_parent = bone_attachment; } - - // We still have not managed to make a node if (gltf_node->mesh >= 0) { current_node = _generate_mesh_instance(state, scene_parent, node_index); } else if (gltf_node->camera >= 0) { current_node = _generate_camera(state, scene_parent, node_index); } else if (gltf_node->light >= 0) { current_node = _generate_light(state, scene_parent, node_index); - } else { + } + + // We still have not managed to make a node. + if (!current_node) { current_node = _generate_spatial(state, scene_parent, node_index); } diff --git a/modules/navigation/navigation_mesh_generator.cpp b/modules/navigation/navigation_mesh_generator.cpp index 41cd75fd22..905d10c9d4 100644 --- a/modules/navigation/navigation_mesh_generator.cpp +++ b/modules/navigation/navigation_mesh_generator.cpp @@ -138,7 +138,7 @@ void NavigationMeshGenerator::_add_faces(const PackedVector3Array &p_faces, cons } } -void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transform, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices, int p_generate_from, uint32_t p_collision_mask, bool p_recurse_children) { +void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transform, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children) { if (Object::cast_to<MeshInstance3D>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) { MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(p_node); Ref<Mesh> mesh = mesh_instance->get_mesh(); @@ -187,7 +187,7 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor Ref<CapsuleMesh> capsule_mesh; capsule_mesh.instantiate(); capsule_mesh->set_radius(capsule->get_radius()); - capsule_mesh->set_mid_height(capsule->get_height() / 2.0); + capsule_mesh->set_height(capsule->get_height()); mesh = capsule_mesh; } @@ -515,7 +515,7 @@ void NavigationMeshGenerator::bake(Ref<NavigationMesh> p_nav_mesh, Node *p_node) Transform3D navmesh_xform = Object::cast_to<Node3D>(p_node)->get_transform().affine_inverse(); for (Node *E : parse_nodes) { - int geometry_type = p_nav_mesh->get_parsed_geometry_type(); + NavigationMesh::ParsedGeometryType geometry_type = p_nav_mesh->get_parsed_geometry_type(); uint32_t collision_mask = p_nav_mesh->get_collision_mask(); bool recurse_children = p_nav_mesh->get_source_geometry_mode() != NavigationMesh::SOURCE_GEOMETRY_GROUPS_EXPLICIT; _parse_geometry(navmesh_xform, E, vertices, indices, geometry_type, collision_mask, recurse_children); diff --git a/modules/navigation/navigation_mesh_generator.h b/modules/navigation/navigation_mesh_generator.h index 847c7d097b..78f1329e3f 100644 --- a/modules/navigation/navigation_mesh_generator.h +++ b/modules/navigation/navigation_mesh_generator.h @@ -52,7 +52,7 @@ protected: static void _add_vertex(const Vector3 &p_vec3, Vector<float> &p_verticies); static void _add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform, Vector<float> &p_verticies, Vector<int> &p_indices); static void _add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform, Vector<float> &p_verticies, Vector<int> &p_indices); - static void _parse_geometry(Transform3D p_accumulated_transform, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices, int p_generate_from, uint32_t p_collision_mask, bool p_recurse_children); + static void _parse_geometry(Transform3D p_accumulated_transform, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children); static void _convert_detail_mesh_to_native_navigation_mesh(const rcPolyMeshDetail *p_detail_mesh, Ref<NavigationMesh> p_nav_mesh); static void _build_recast_navigation_mesh( diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index fa4888f843..9ecb0de5b8 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -977,6 +977,7 @@ void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced * p_shaped->sort_valid = false; p_shaped->line_breaks_valid = false; p_shaped->justification_ops_valid = false; + p_shaped->text_trimmed = false; p_shaped->ascent = 0.f; p_shaped->descent = 0.f; p_shaped->width = 0.f; @@ -984,6 +985,7 @@ void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced * p_shaped->uthk = 0.f; p_shaped->glyphs.clear(); p_shaped->glyphs_logical.clear(); + p_shaped->overrun_trim_data = TrimData(); p_shaped->utf16 = Char16String(); if (p_shaped->script_iter != nullptr) { memdelete(p_shaped->script_iter); @@ -1161,7 +1163,7 @@ bool TextServerAdvanced::shaped_text_add_string(RID p_shaped, const String &p_te return true; } -bool TextServerAdvanced::shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, VAlign p_inline_align, int p_length) { +bool TextServerAdvanced::shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlign p_inline_align, int p_length) { _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); @@ -1191,7 +1193,7 @@ bool TextServerAdvanced::shaped_text_add_object(RID p_shaped, Variant p_key, con return true; } -bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, VAlign p_inline_align) { +bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlign p_inline_align) { _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); @@ -1222,34 +1224,10 @@ bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, if (sd->orientation == ORIENTATION_HORIZONTAL) { sd->objects[key].rect.position.x = sd->width; sd->width += sd->objects[key].rect.size.x; - switch (sd->objects[key].inline_align) { - case VALIGN_TOP: { - sd->ascent = MAX(sd->ascent, sd->objects[key].rect.size.y); - } break; - case VALIGN_CENTER: { - sd->ascent = MAX(sd->ascent, Math::round(sd->objects[key].rect.size.y / 2)); - sd->descent = MAX(sd->descent, Math::round(sd->objects[key].rect.size.y / 2)); - } break; - case VALIGN_BOTTOM: { - sd->descent = MAX(sd->descent, sd->objects[key].rect.size.y); - } break; - } sd->glyphs.write[i].advance = sd->objects[key].rect.size.x; } else { sd->objects[key].rect.position.y = sd->width; sd->width += sd->objects[key].rect.size.y; - switch (sd->objects[key].inline_align) { - case VALIGN_TOP: { - sd->ascent = MAX(sd->ascent, sd->objects[key].rect.size.x); - } break; - case VALIGN_CENTER: { - sd->ascent = MAX(sd->ascent, Math::round(sd->objects[key].rect.size.x / 2)); - sd->descent = MAX(sd->descent, Math::round(sd->objects[key].rect.size.x / 2)); - } break; - case VALIGN_BOTTOM: { - sd->descent = MAX(sd->descent, sd->objects[key].rect.size.x); - } break; - } sd->glyphs.write[i].advance = sd->objects[key].rect.size.y; } } else { @@ -1279,35 +1257,71 @@ bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, } // Align embedded objects to baseline. + float full_ascent = sd->ascent; + float full_descent = sd->descent; for (Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = sd->objects.front(); E; E = E->next()) { if ((E->get().pos >= sd->start) && (E->get().pos < sd->end)) { if (sd->orientation == ORIENTATION_HORIZONTAL) { - switch (E->get().inline_align) { - case VALIGN_TOP: { + switch (E->get().inline_align & INLINE_ALIGN_TEXT_MASK) { + case INLINE_ALIGN_TO_TOP: { E->get().rect.position.y = -sd->ascent; } break; - case VALIGN_CENTER: { - E->get().rect.position.y = -(E->get().rect.size.y / 2); + case INLINE_ALIGN_TO_CENTER: { + E->get().rect.position.y = (-sd->ascent + sd->descent) / 2; + } break; + case INLINE_ALIGN_TO_BASELINE: { + E->get().rect.position.y = 0; } break; - case VALIGN_BOTTOM: { - E->get().rect.position.y = sd->descent - E->get().rect.size.y; + case INLINE_ALIGN_TO_BOTTOM: { + E->get().rect.position.y = sd->descent; } break; } + switch (E->get().inline_align & INLINE_ALIGN_IMAGE_MASK) { + case INLINE_ALIGN_BOTTOM_TO: { + E->get().rect.position.y -= E->get().rect.size.y; + } break; + case INLINE_ALIGN_CENTER_TO: { + E->get().rect.position.y -= E->get().rect.size.y / 2; + } break; + case INLINE_ALIGN_TOP_TO: { + //NOP + } break; + } + full_ascent = MAX(full_ascent, -E->get().rect.position.y); + full_descent = MAX(full_descent, E->get().rect.position.y + E->get().rect.size.y); } else { - switch (E->get().inline_align) { - case VALIGN_TOP: { + switch (E->get().inline_align & INLINE_ALIGN_TEXT_MASK) { + case INLINE_ALIGN_TO_TOP: { E->get().rect.position.x = -sd->ascent; } break; - case VALIGN_CENTER: { - E->get().rect.position.x = -(E->get().rect.size.x / 2); + case INLINE_ALIGN_TO_CENTER: { + E->get().rect.position.x = (-sd->ascent + sd->descent) / 2; + } break; + case INLINE_ALIGN_TO_BASELINE: { + E->get().rect.position.x = 0; + } break; + case INLINE_ALIGN_TO_BOTTOM: { + E->get().rect.position.x = sd->descent; + } break; + } + switch (E->get().inline_align & INLINE_ALIGN_IMAGE_MASK) { + case INLINE_ALIGN_BOTTOM_TO: { + E->get().rect.position.x -= E->get().rect.size.x; + } break; + case INLINE_ALIGN_CENTER_TO: { + E->get().rect.position.x -= E->get().rect.size.x / 2; } break; - case VALIGN_BOTTOM: { - E->get().rect.position.x = sd->descent - E->get().rect.size.x; + case INLINE_ALIGN_TOP_TO: { + //NOP } break; } + full_ascent = MAX(full_ascent, -E->get().rect.position.x); + full_descent = MAX(full_descent, E->get().rect.position.x + E->get().rect.size.x); } } } + sd->ascent = full_ascent; + sd->descent = full_descent; } return true; } @@ -1363,7 +1377,7 @@ RID TextServerAdvanced::shaped_text_substr(RID p_shaped, int p_start, int p_leng ERR_FAIL_COND_V_MSG((start < 0 || end - start > new_sd->utf16.length()), RID(), "Invalid BiDi override range."); - //Create temporary line bidi & shape + // Create temporary line bidi & shape. UBiDi *bidi_iter = ubidi_openSized(end - start, 0, &err); ERR_FAIL_COND_V_MSG(U_FAILURE(err), RID(), u_errorName(err)); ubidi_setLine(sd->bidi_iter[ov], start, end, bidi_iter, &err); @@ -1404,33 +1418,9 @@ RID TextServerAdvanced::shaped_text_substr(RID p_shaped, int p_start, int p_leng if (new_sd->orientation == ORIENTATION_HORIZONTAL) { new_sd->objects[key].rect.position.x = new_sd->width; new_sd->width += new_sd->objects[key].rect.size.x; - switch (new_sd->objects[key].inline_align) { - case VALIGN_TOP: { - new_sd->ascent = MAX(new_sd->ascent, new_sd->objects[key].rect.size.y); - } break; - case VALIGN_CENTER: { - new_sd->ascent = MAX(new_sd->ascent, Math::round(new_sd->objects[key].rect.size.y / 2)); - new_sd->descent = MAX(new_sd->descent, Math::round(new_sd->objects[key].rect.size.y / 2)); - } break; - case VALIGN_BOTTOM: { - new_sd->descent = MAX(new_sd->descent, new_sd->objects[key].rect.size.y); - } break; - } } else { new_sd->objects[key].rect.position.y = new_sd->width; new_sd->width += new_sd->objects[key].rect.size.y; - switch (new_sd->objects[key].inline_align) { - case VALIGN_TOP: { - new_sd->ascent = MAX(new_sd->ascent, new_sd->objects[key].rect.size.x); - } break; - case VALIGN_CENTER: { - new_sd->ascent = MAX(new_sd->ascent, Math::round(new_sd->objects[key].rect.size.x / 2)); - new_sd->descent = MAX(new_sd->descent, Math::round(new_sd->objects[key].rect.size.x / 2)); - } break; - case VALIGN_BOTTOM: { - new_sd->descent = MAX(new_sd->descent, new_sd->objects[key].rect.size.x); - } break; - } } } else { if (prev_rid != gl.font_rid) { @@ -1464,37 +1454,72 @@ RID TextServerAdvanced::shaped_text_substr(RID p_shaped, int p_start, int p_leng } // Align embedded objects to baseline. + float full_ascent = new_sd->ascent; + float full_descent = new_sd->descent; for (Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = new_sd->objects.front(); E; E = E->next()) { if ((E->get().pos >= new_sd->start) && (E->get().pos < new_sd->end)) { if (sd->orientation == ORIENTATION_HORIZONTAL) { - switch (E->get().inline_align) { - case VALIGN_TOP: { + switch (E->get().inline_align & INLINE_ALIGN_TEXT_MASK) { + case INLINE_ALIGN_TO_TOP: { E->get().rect.position.y = -new_sd->ascent; } break; - case VALIGN_CENTER: { - E->get().rect.position.y = -(E->get().rect.size.y / 2); + case INLINE_ALIGN_TO_CENTER: { + E->get().rect.position.y = (-new_sd->ascent + new_sd->descent) / 2; + } break; + case INLINE_ALIGN_TO_BASELINE: { + E->get().rect.position.y = 0; } break; - case VALIGN_BOTTOM: { - E->get().rect.position.y = new_sd->descent - E->get().rect.size.y; + case INLINE_ALIGN_TO_BOTTOM: { + E->get().rect.position.y = new_sd->descent; } break; } + switch (E->get().inline_align & INLINE_ALIGN_IMAGE_MASK) { + case INLINE_ALIGN_BOTTOM_TO: { + E->get().rect.position.y -= E->get().rect.size.y; + } break; + case INLINE_ALIGN_CENTER_TO: { + E->get().rect.position.y -= E->get().rect.size.y / 2; + } break; + case INLINE_ALIGN_TOP_TO: { + //NOP + } break; + } + full_ascent = MAX(full_ascent, -E->get().rect.position.y); + full_descent = MAX(full_descent, E->get().rect.position.y + E->get().rect.size.y); } else { - switch (E->get().inline_align) { - case VALIGN_TOP: { + switch (E->get().inline_align & INLINE_ALIGN_TEXT_MASK) { + case INLINE_ALIGN_TO_TOP: { E->get().rect.position.x = -new_sd->ascent; } break; - case VALIGN_CENTER: { - E->get().rect.position.x = -(E->get().rect.size.x / 2); + case INLINE_ALIGN_TO_CENTER: { + E->get().rect.position.x = (-new_sd->ascent + new_sd->descent) / 2; + } break; + case INLINE_ALIGN_TO_BASELINE: { + E->get().rect.position.x = 0; } break; - case VALIGN_BOTTOM: { - E->get().rect.position.x = new_sd->descent - E->get().rect.size.x; + case INLINE_ALIGN_TO_BOTTOM: { + E->get().rect.position.x = new_sd->descent; } break; } + switch (E->get().inline_align & INLINE_ALIGN_IMAGE_MASK) { + case INLINE_ALIGN_BOTTOM_TO: { + E->get().rect.position.x -= E->get().rect.size.x; + } break; + case INLINE_ALIGN_CENTER_TO: { + E->get().rect.position.x -= E->get().rect.size.x / 2; + } break; + case INLINE_ALIGN_TOP_TO: { + //NOP + } break; + } + full_ascent = MAX(full_ascent, -E->get().rect.position.x); + full_descent = MAX(full_descent, E->get().rect.position.x + E->get().rect.size.x); } } } + new_sd->ascent = full_ascent; + new_sd->descent = full_descent; } - new_sd->valid = true; return shaped_owner.make_rid(new_sd); @@ -1518,6 +1543,7 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width, const_cast<TextServerAdvanced *>(this)->shaped_text_update_justification_ops(p_shaped); } + sd->fit_width_minimum_reached = false; int start_pos = 0; int end_pos = sd->glyphs.size() - 1; @@ -1546,14 +1572,26 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width, } } + float justification_width; + if ((p_jst_flags & JUSTIFICATION_CONSTRAIN_ELLIPSIS) == JUSTIFICATION_CONSTRAIN_ELLIPSIS) { + if (sd->overrun_trim_data.trim_pos >= 0) { + start_pos = sd->overrun_trim_data.trim_pos; + justification_width = sd->width_trimmed; + } else { + return sd->width; + } + } else { + justification_width = sd->width; + } + if ((p_jst_flags & JUSTIFICATION_TRIM_EDGE_SPACES) == JUSTIFICATION_TRIM_EDGE_SPACES) { while ((start_pos < end_pos) && ((sd->glyphs[start_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) { - sd->width -= sd->glyphs[start_pos].advance * sd->glyphs[start_pos].repeat; + justification_width -= sd->glyphs[start_pos].advance * sd->glyphs[start_pos].repeat; sd->glyphs.write[start_pos].advance = 0; start_pos += sd->glyphs[start_pos].count; } while ((start_pos < end_pos) && ((sd->glyphs[end_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (sd->glyphs[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (sd->glyphs[end_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) { - sd->width -= sd->glyphs[end_pos].advance * sd->glyphs[end_pos].repeat; + justification_width -= sd->glyphs[end_pos].advance * sd->glyphs[end_pos].repeat; sd->glyphs.write[end_pos].advance = 0; end_pos -= sd->glyphs[end_pos].count; } @@ -1574,7 +1612,7 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width, } if ((elongation_count > 0) && ((p_jst_flags & JUSTIFICATION_KASHIDA) == JUSTIFICATION_KASHIDA)) { - float delta_width_per_kashida = (p_width - sd->width) / elongation_count; + float delta_width_per_kashida = (p_width - justification_width) / elongation_count; for (int i = start_pos; i <= end_pos; i++) { Glyph &gl = sd->glyphs.write[i]; if (gl.count > 0) { @@ -1582,34 +1620,50 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width, int count = delta_width_per_kashida / gl.advance; int prev_count = gl.repeat; if ((gl.flags & GRAPHEME_IS_VIRTUAL) == GRAPHEME_IS_VIRTUAL) { - gl.repeat = count; - } else { - gl.repeat = count + 1; + gl.repeat = MAX(count, 0); } - sd->width += (gl.repeat - prev_count) * gl.advance; + justification_width += (gl.repeat - prev_count) * gl.advance; } } } } - + float adv_remain = 0; if ((space_count > 0) && ((p_jst_flags & JUSTIFICATION_WORD_BOUND) == JUSTIFICATION_WORD_BOUND)) { - float delta_width_per_space = (p_width - sd->width) / space_count; + float delta_width_per_space = (p_width - justification_width) / space_count; for (int i = start_pos; i <= end_pos; i++) { Glyph &gl = sd->glyphs.write[i]; if (gl.count > 0) { if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE) { float old_adv = gl.advance; + float new_advance; if ((gl.flags & GRAPHEME_IS_VIRTUAL) == GRAPHEME_IS_VIRTUAL) { - gl.advance = Math::round(MAX(gl.advance + delta_width_per_space, 0.f)); + new_advance = MAX(gl.advance + delta_width_per_space, 0.f); } else { - gl.advance = Math::round(MAX(gl.advance + delta_width_per_space, 0.05 * gl.font_size)); + new_advance = MAX(gl.advance + delta_width_per_space, 0.05 * gl.font_size); + } + gl.advance = new_advance; + adv_remain += (new_advance - gl.advance); + if (adv_remain >= 1.0) { + gl.advance++; + adv_remain -= 1.0; + } else if (adv_remain <= -1.0) { + gl.advance = MAX(gl.advance - 1, 0); + adv_remain -= 1.0; } - sd->width += (gl.advance - old_adv); + justification_width += (gl.advance - old_adv); } } } } + if (Math::floor(p_width) < Math::floor(justification_width)) { + sd->fit_width_minimum_reached = true; + } + + if ((p_jst_flags & JUSTIFICATION_CONSTRAIN_ELLIPSIS) != JUSTIFICATION_CONSTRAIN_ELLIPSIS) { + sd->width = justification_width; + } + return sd->width; } @@ -1662,7 +1716,7 @@ float TextServerAdvanced::shaped_text_tab_align(RID p_shaped, const Vector<float return 0.f; } -void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint8_t p_clip_flags) { +void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint8_t p_trim_flags) { _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped_line); ERR_FAIL_COND_MSG(!sd, "ShapedTextDataAdvanced invalid."); @@ -1670,13 +1724,22 @@ void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl shaped_text_shape(p_shaped_line); } - bool add_ellipsis = (p_clip_flags & OVERRUN_ADD_ELLIPSIS) == OVERRUN_ADD_ELLIPSIS; - bool cut_per_word = (p_clip_flags & OVERRUN_TRIM_WORD_ONLY) == OVERRUN_TRIM_WORD_ONLY; - bool enforce_ellipsis = (p_clip_flags & OVERRUN_ENFORCE_ELLIPSIS) == OVERRUN_ENFORCE_ELLIPSIS; + sd->overrun_trim_data.ellipsis_glyph_buf.clear(); + + bool add_ellipsis = (p_trim_flags & OVERRUN_ADD_ELLIPSIS) == OVERRUN_ADD_ELLIPSIS; + bool cut_per_word = (p_trim_flags & OVERRUN_TRIM_WORD_ONLY) == OVERRUN_TRIM_WORD_ONLY; + bool enforce_ellipsis = (p_trim_flags & OVERRUN_ENFORCE_ELLIPSIS) == OVERRUN_ENFORCE_ELLIPSIS; + bool justification_aware = (p_trim_flags & OVERRUN_JUSTIFICATION_AWARE) == OVERRUN_JUSTIFICATION_AWARE; Glyph *sd_glyphs = sd->glyphs.ptrw(); - if ((p_clip_flags & OVERRUN_TRIM) == OVERRUN_NO_TRIMMING || sd_glyphs == nullptr || p_width <= 0 || !(sd->width > p_width || enforce_ellipsis)) { + if ((p_trim_flags & OVERRUN_TRIM) == OVERRUN_NO_TRIMMING || sd_glyphs == nullptr || p_width <= 0 || !(sd->width > p_width || enforce_ellipsis)) { + sd->overrun_trim_data.trim_pos = -1; + sd->overrun_trim_data.ellipsis_pos = -1; + return; + } + + if (justification_aware && !sd->fit_width_minimum_reached) { return; } @@ -1688,9 +1751,9 @@ void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl uint32_t whitespace_gl_idx = font_get_glyph_index(last_gl_font_rid, ' '); Vector2 whitespace_adv = font_get_glyph_advance(last_gl_font_rid, whitespace_gl_idx, last_gl_font_size); - int ellipsis_advance = 0; + int ellipsis_width = 0; if (add_ellipsis) { - ellipsis_advance = 3 * dot_adv.x + font_get_spacing_glyph(last_gl_font_rid) + (cut_per_word ? whitespace_adv.x : 0); + ellipsis_width = 3 * dot_adv.x + font_get_spacing_glyph(last_gl_font_rid) + (cut_per_word ? whitespace_adv.x : 0); } int ell_min_characters = 6; @@ -1710,12 +1773,12 @@ void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl for (int i = glyphs_from; i != glyphs_to; i += glyphs_delta) { if (!is_rtl) { - width -= sd_glyphs[i].advance; + width -= sd_glyphs[i].advance * sd_glyphs[i].repeat; } if (sd_glyphs[i].count > 0) { bool above_min_char_treshold = ((is_rtl) ? sd_size - 1 - i : i) >= ell_min_characters; - if (width + (((above_min_char_treshold && add_ellipsis) || enforce_ellipsis) ? ellipsis_advance : 0) <= p_width) { + if (width + (((above_min_char_treshold && add_ellipsis) || enforce_ellipsis) ? ellipsis_width : 0) <= p_width) { if (cut_per_word && above_min_char_treshold) { if ((sd_glyphs[i].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT) { last_valid_cut = i; @@ -1728,7 +1791,7 @@ void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl if (found) { trim_pos = last_valid_cut; - if (above_min_char_treshold && width - ellipsis_advance <= p_width) { + if (add_ellipsis && (above_min_char_treshold || enforce_ellipsis) && width - ellipsis_width <= p_width) { ellipsis_pos = trim_pos; } break; @@ -1736,18 +1799,21 @@ void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl } } if (is_rtl) { - width -= sd_glyphs[i].advance; + width -= sd_glyphs[i].advance * sd_glyphs[i].repeat; } } + sd->overrun_trim_data.trim_pos = trim_pos; + sd->overrun_trim_data.ellipsis_pos = ellipsis_pos; + if (trim_pos == 0 && enforce_ellipsis && add_ellipsis) { + sd->overrun_trim_data.ellipsis_pos = 0; + } + if ((trim_pos >= 0 && sd->width > p_width) || enforce_ellipsis) { - int added_glyphs = 0; if (add_ellipsis && (ellipsis_pos > 0 || enforce_ellipsis)) { // Insert an additional space when cutting word bound for aesthetics. if (cut_per_word && (ellipsis_pos > 0)) { TextServer::Glyph gl; - gl.start = sd_glyphs[ellipsis_pos].start; - gl.end = sd_glyphs[ellipsis_pos].end; gl.count = 1; gl.advance = whitespace_adv.x; gl.index = whitespace_gl_idx; @@ -1755,68 +1821,33 @@ void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl gl.font_size = last_gl_font_size; gl.flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL | (is_rtl ? GRAPHEME_IS_RTL : 0); - // Optimized glyph insertion by replacing a glyph whenever possible. - int glyph_idx = trim_pos + ((is_rtl) ? (-added_glyphs - 1) : added_glyphs); - if (is_rtl) { - if (glyph_idx < 0) { - sd->glyphs.insert(0, gl); - } else { - sd->glyphs.set(glyph_idx, gl); - } - } else { - if (glyph_idx > (sd_size - 1)) { - sd->glyphs.append(gl); - } else { - sd->glyphs.set(glyph_idx, gl); - } - } - added_glyphs++; + sd->overrun_trim_data.ellipsis_glyph_buf.append(gl); } // Add ellipsis dots. - for (int d = 0; d < 3; d++) { - TextServer::Glyph gl; - gl.start = sd_glyphs[ellipsis_pos].start; - gl.end = sd_glyphs[ellipsis_pos].end; - gl.count = 1; - gl.advance = dot_adv.x; - gl.index = dot_gl_idx; - gl.font_rid = last_gl_font_rid; - gl.font_size = last_gl_font_size; - gl.flags = GRAPHEME_IS_PUNCTUATION | GRAPHEME_IS_VIRTUAL | (is_rtl ? GRAPHEME_IS_RTL : 0); - - // Optimized glyph insertion by replacing a glyph whenever possible. - int glyph_idx = trim_pos + ((is_rtl) ? (-added_glyphs - 1) : added_glyphs); - if (is_rtl) { - if (glyph_idx < 0) { - sd->glyphs.insert(0, gl); - } else { - sd->glyphs.set(glyph_idx, gl); - } - } else { - if (glyph_idx > (sd_size - 1)) { - sd->glyphs.append(gl); - } else { - sd->glyphs.set(glyph_idx, gl); - } - } - added_glyphs++; - } + TextServer::Glyph gl; + gl.count = 1; + gl.repeat = 3; + gl.advance = dot_adv.x; + gl.index = dot_gl_idx; + gl.font_rid = last_gl_font_rid; + gl.font_size = last_gl_font_size; + gl.flags = GRAPHEME_IS_PUNCTUATION | GRAPHEME_IS_VIRTUAL | (is_rtl ? GRAPHEME_IS_RTL : 0); + + sd->overrun_trim_data.ellipsis_glyph_buf.append(gl); } - // Cut the remaining glyphs off. - if (!is_rtl) { - sd->glyphs.resize(trim_pos + added_glyphs); - } else { - if (trim_pos - added_glyphs >= 0) { - sd->glyphs = sd->glyphs.subarray(trim_pos - added_glyphs, sd->glyphs.size() - 1); - } - } - - // Update to correct width. - sd->width = width + ((ellipsis_pos != -1) ? ellipsis_advance : 0); + sd->text_trimmed = true; + sd->width_trimmed = width + ((ellipsis_pos != -1) ? ellipsis_width : 0); } } +TextServer::TrimData TextServerAdvanced::shaped_text_get_trim_data(RID p_shaped) const { + _THREAD_SAFE_METHOD_ + ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); + ERR_FAIL_COND_V_MSG(!sd, TrimData(), "ShapedTextDataAdvanced invalid."); + return sd->overrun_trim_data; +} + bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) { _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); @@ -1843,7 +1874,7 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) { int r_end = sd->spans[i].end; UBreakIterator *bi = ubrk_open(UBRK_LINE, language.ascii().get_data(), data + _convert_pos_inv(sd, r_start), _convert_pos_inv(sd, r_end - r_start), &err); if (U_FAILURE(err)) { - //No data loaded - use fallback. + // No data loaded - use fallback. for (int j = r_start; j < r_end; j++) { char32_t c = sd->text[j - sd->start]; if (is_whitespace(c)) { @@ -1907,7 +1938,7 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) { gl.font_rid = sd_glyphs[i].font_rid; gl.font_size = sd_glyphs[i].font_size; gl.flags = GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL; - sd->glyphs.insert(i + sd_glyphs[i].count, gl); // insert after + sd->glyphs.insert(i + sd_glyphs[i].count, gl); // Insert after. // Update write pointer and size. sd_size = sd->glyphs.size(); @@ -2027,7 +2058,7 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) { UErrorCode err = U_ZERO_ERROR; UBreakIterator *bi = ubrk_open(UBRK_WORD, "", data, data_size, &err); if (U_FAILURE(err)) { - // No data - use fallback + // No data - use fallback. int limit = 0; for (int i = 0; i < sd->text.length(); i++) { if (is_whitespace(data[i])) { @@ -2100,7 +2131,7 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) { gl.font_rid = sd->glyphs[i].font_rid; gl.font_size = sd->glyphs[i].font_size; gl.flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_VIRTUAL; - sd->glyphs.insert(i + sd->glyphs[i].count, gl); // insert after + sd->glyphs.insert(i + sd->glyphs[i].count, gl); // Insert after. i += sd->glyphs[i].count; continue; } @@ -2166,7 +2197,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_star int fs = p_sd->spans[p_span].font_size; if (fd == nullptr) { - // Add fallback glyphs + // Add fallback glyphs. for (int i = p_start; i < p_end; i++) { if (p_sd->preserve_invalid || (p_sd->preserve_control && is_control(p_sd->text[i]))) { TextServer::Glyph gl; @@ -2309,7 +2340,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_star w[last_cluster_index].flags |= GRAPHEME_IS_VALID; } - //Fallback. + // Fallback. int failed_subrun_start = p_end + 1; int failed_subrun_end = p_start; @@ -2473,33 +2504,9 @@ bool TextServerAdvanced::shaped_text_shape(RID p_shaped) { if (sd->orientation == ORIENTATION_HORIZONTAL) { sd->objects[span.embedded_key].rect.position.x = sd->width; sd->width += sd->objects[span.embedded_key].rect.size.x; - switch (sd->objects[span.embedded_key].inline_align) { - case VALIGN_TOP: { - sd->ascent = MAX(sd->ascent, sd->objects[span.embedded_key].rect.size.y); - } break; - case VALIGN_CENTER: { - sd->ascent = MAX(sd->ascent, Math::round(sd->objects[span.embedded_key].rect.size.y / 2)); - sd->descent = MAX(sd->descent, Math::round(sd->objects[span.embedded_key].rect.size.y / 2)); - } break; - case VALIGN_BOTTOM: { - sd->descent = MAX(sd->descent, sd->objects[span.embedded_key].rect.size.y); - } break; - } } else { sd->objects[span.embedded_key].rect.position.y = sd->width; sd->width += sd->objects[span.embedded_key].rect.size.y; - switch (sd->objects[span.embedded_key].inline_align) { - case VALIGN_TOP: { - sd->ascent = MAX(sd->ascent, sd->objects[span.embedded_key].rect.size.x); - } break; - case VALIGN_CENTER: { - sd->ascent = MAX(sd->ascent, Math::round(sd->objects[span.embedded_key].rect.size.x / 2)); - sd->descent = MAX(sd->descent, Math::round(sd->objects[span.embedded_key].rect.size.x / 2)); - } break; - case VALIGN_BOTTOM: { - sd->descent = MAX(sd->descent, sd->objects[span.embedded_key].rect.size.x); - } break; - } } Glyph gl; gl.start = span.start; @@ -2539,34 +2546,69 @@ bool TextServerAdvanced::shaped_text_shape(RID p_shaped) { } // Align embedded objects to baseline. + float full_ascent = sd->ascent; + float full_descent = sd->descent; for (Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = sd->objects.front(); E; E = E->next()) { if (sd->orientation == ORIENTATION_HORIZONTAL) { - switch (E->get().inline_align) { - case VALIGN_TOP: { + switch (E->get().inline_align & INLINE_ALIGN_TEXT_MASK) { + case INLINE_ALIGN_TO_TOP: { E->get().rect.position.y = -sd->ascent; } break; - case VALIGN_CENTER: { - E->get().rect.position.y = -(E->get().rect.size.y / 2); + case INLINE_ALIGN_TO_CENTER: { + E->get().rect.position.y = (-sd->ascent + sd->descent) / 2; + } break; + case INLINE_ALIGN_TO_BASELINE: { + E->get().rect.position.y = 0; } break; - case VALIGN_BOTTOM: { - E->get().rect.position.y = sd->descent - E->get().rect.size.y; + case INLINE_ALIGN_TO_BOTTOM: { + E->get().rect.position.y = sd->descent; } break; } + switch (E->get().inline_align & INLINE_ALIGN_IMAGE_MASK) { + case INLINE_ALIGN_BOTTOM_TO: { + E->get().rect.position.y -= E->get().rect.size.y; + } break; + case INLINE_ALIGN_CENTER_TO: { + E->get().rect.position.y -= E->get().rect.size.y / 2; + } break; + case INLINE_ALIGN_TOP_TO: { + //NOP + } break; + } + full_ascent = MAX(full_ascent, -E->get().rect.position.y); + full_descent = MAX(full_descent, E->get().rect.position.y + E->get().rect.size.y); } else { - switch (E->get().inline_align) { - case VALIGN_TOP: { + switch (E->get().inline_align & INLINE_ALIGN_TEXT_MASK) { + case INLINE_ALIGN_TO_TOP: { E->get().rect.position.x = -sd->ascent; } break; - case VALIGN_CENTER: { - E->get().rect.position.x = -(E->get().rect.size.x / 2); + case INLINE_ALIGN_TO_CENTER: { + E->get().rect.position.x = (-sd->ascent + sd->descent) / 2; + } break; + case INLINE_ALIGN_TO_BASELINE: { + E->get().rect.position.x = 0; } break; - case VALIGN_BOTTOM: { - E->get().rect.position.x = sd->descent - E->get().rect.size.x; + case INLINE_ALIGN_TO_BOTTOM: { + E->get().rect.position.x = sd->descent; } break; } + switch (E->get().inline_align & INLINE_ALIGN_IMAGE_MASK) { + case INLINE_ALIGN_BOTTOM_TO: { + E->get().rect.position.x -= E->get().rect.size.x; + } break; + case INLINE_ALIGN_CENTER_TO: { + E->get().rect.position.x -= E->get().rect.size.x / 2; + } break; + case INLINE_ALIGN_TOP_TO: { + //NOP + } break; + } + full_ascent = MAX(full_ascent, -E->get().rect.position.x); + full_descent = MAX(full_descent, E->get().rect.position.x + E->get().rect.size.x); } } - + sd->ascent = full_ascent; + sd->descent = full_descent; sd->valid = true; return sd->valid; } @@ -2643,9 +2685,9 @@ Size2 TextServerAdvanced::shaped_text_get_size(RID p_shaped) const { const_cast<TextServerAdvanced *>(this)->shaped_text_shape(p_shaped); } if (sd->orientation == TextServer::ORIENTATION_HORIZONTAL) { - return Size2(sd->width, sd->ascent + sd->descent); + return Size2((sd->text_trimmed ? sd->width_trimmed : sd->width), sd->ascent + sd->descent); } else { - return Size2(sd->ascent + sd->descent, sd->width); + return Size2(sd->ascent + sd->descent, (sd->text_trimmed ? sd->width_trimmed : sd->width)); } } @@ -2676,7 +2718,7 @@ float TextServerAdvanced::shaped_text_get_width(RID p_shaped) const { if (!sd->valid) { const_cast<TextServerAdvanced *>(this)->shaped_text_shape(p_shaped); } - return sd->width; + return (sd->text_trimmed ? sd->width_trimmed : sd->width); } float TextServerAdvanced::shaped_text_get_underline_position(RID p_shaped) const { diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index 3c4f840bfd..d19ba41a1a 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -216,8 +216,8 @@ public: virtual bool shaped_text_get_preserve_control(RID p_shaped) const override; virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "") override; - virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, VAlign p_inline_align = VALIGN_CENTER, int p_length = 1) override; - virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, VAlign p_inline_align = VALIGN_CENTER) override; + virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlign p_inline_align = INLINE_ALIGN_CENTER, int p_length = 1) override; + virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlign p_inline_align = INLINE_ALIGN_CENTER) override; virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override; virtual RID shaped_text_get_parent(RID p_shaped) const override; @@ -229,7 +229,8 @@ public: virtual bool shaped_text_update_breaks(RID p_shaped) override; virtual bool shaped_text_update_justification_ops(RID p_shaped) override; - virtual void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_clip_flags) override; + virtual void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_trim_flags) override; + virtual TrimData shaped_text_get_trim_data(RID p_shaped) const override; virtual bool shaped_text_is_ready(RID p_shaped) const override; diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 004cbc2bb3..110194c373 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -570,7 +570,7 @@ void TextServerFallback::shaped_text_set_orientation(RID p_shaped, TextServer::O } void TextServerFallback::shaped_text_set_bidi_override(RID p_shaped, const Vector<Vector2i> &p_override) { - //No BiDi support, ignore. + // No BiDi support, ignore. } TextServer::Orientation TextServerFallback::shaped_text_get_orientation(RID p_shaped) const { @@ -661,7 +661,7 @@ bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_te return true; } -bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, VAlign p_inline_align, int p_length) { +bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlign p_inline_align, int p_length) { _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); @@ -691,7 +691,7 @@ bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, con return true; } -bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, VAlign p_inline_align) { +bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlign p_inline_align) { _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped); ERR_FAIL_COND_V(!sd, false); @@ -724,34 +724,10 @@ bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, if (sd->orientation == ORIENTATION_HORIZONTAL) { sd->objects[key].rect.position.x = sd->width; sd->width += sd->objects[key].rect.size.x; - switch (sd->objects[key].inline_align) { - case VALIGN_TOP: { - sd->ascent = MAX(sd->ascent, sd->objects[key].rect.size.y); - } break; - case VALIGN_CENTER: { - sd->ascent = MAX(sd->ascent, Math::round(sd->objects[key].rect.size.y / 2)); - sd->descent = MAX(sd->descent, Math::round(sd->objects[key].rect.size.y / 2)); - } break; - case VALIGN_BOTTOM: { - sd->descent = MAX(sd->descent, sd->objects[key].rect.size.y); - } break; - } sd->glyphs.write[i].advance = sd->objects[key].rect.size.x; } else { sd->objects[key].rect.position.y = sd->width; sd->width += sd->objects[key].rect.size.y; - switch (sd->objects[key].inline_align) { - case VALIGN_TOP: { - sd->ascent = MAX(sd->ascent, sd->objects[key].rect.size.x); - } break; - case VALIGN_CENTER: { - sd->ascent = MAX(sd->ascent, Math::round(sd->objects[key].rect.size.x / 2)); - sd->descent = MAX(sd->descent, Math::round(sd->objects[key].rect.size.x / 2)); - } break; - case VALIGN_BOTTOM: { - sd->descent = MAX(sd->descent, sd->objects[key].rect.size.x); - } break; - } sd->glyphs.write[i].advance = sd->objects[key].rect.size.y; } } else { @@ -784,35 +760,71 @@ bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, } // Align embedded objects to baseline. + float full_ascent = sd->ascent; + float full_descent = sd->descent; for (Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = sd->objects.front(); E; E = E->next()) { if ((E->get().pos >= sd->start) && (E->get().pos < sd->end)) { if (sd->orientation == ORIENTATION_HORIZONTAL) { - switch (E->get().inline_align) { - case VALIGN_TOP: { + switch (E->get().inline_align & INLINE_ALIGN_TEXT_MASK) { + case INLINE_ALIGN_TO_TOP: { E->get().rect.position.y = -sd->ascent; } break; - case VALIGN_CENTER: { - E->get().rect.position.y = -(E->get().rect.size.y / 2); + case INLINE_ALIGN_TO_CENTER: { + E->get().rect.position.y = (-sd->ascent + sd->descent) / 2; + } break; + case INLINE_ALIGN_TO_BASELINE: { + E->get().rect.position.y = 0; + } break; + case INLINE_ALIGN_TO_BOTTOM: { + E->get().rect.position.y = sd->descent; } break; - case VALIGN_BOTTOM: { - E->get().rect.position.y = sd->descent - E->get().rect.size.y; + } + switch (E->get().inline_align & INLINE_ALIGN_IMAGE_MASK) { + case INLINE_ALIGN_BOTTOM_TO: { + E->get().rect.position.y -= E->get().rect.size.y; + } break; + case INLINE_ALIGN_CENTER_TO: { + E->get().rect.position.y -= E->get().rect.size.y / 2; + } break; + case INLINE_ALIGN_TOP_TO: { + //NOP } break; } + full_ascent = MAX(full_ascent, -E->get().rect.position.y); + full_descent = MAX(full_descent, E->get().rect.position.y + E->get().rect.size.y); } else { - switch (E->get().inline_align) { - case VALIGN_TOP: { + switch (E->get().inline_align & INLINE_ALIGN_TEXT_MASK) { + case INLINE_ALIGN_TO_TOP: { E->get().rect.position.x = -sd->ascent; } break; - case VALIGN_CENTER: { - E->get().rect.position.x = -(E->get().rect.size.x / 2); + case INLINE_ALIGN_TO_CENTER: { + E->get().rect.position.x = (-sd->ascent + sd->descent) / 2; + } break; + case INLINE_ALIGN_TO_BASELINE: { + E->get().rect.position.x = 0; + } break; + case INLINE_ALIGN_TO_BOTTOM: { + E->get().rect.position.x = sd->descent; } break; - case VALIGN_BOTTOM: { - E->get().rect.position.x = sd->descent - E->get().rect.size.x; + } + switch (E->get().inline_align & INLINE_ALIGN_IMAGE_MASK) { + case INLINE_ALIGN_BOTTOM_TO: { + E->get().rect.position.x -= E->get().rect.size.x; + } break; + case INLINE_ALIGN_CENTER_TO: { + E->get().rect.position.x -= E->get().rect.size.x / 2; + } break; + case INLINE_ALIGN_TOP_TO: { + //NOP } break; } + full_ascent = MAX(full_ascent, -E->get().rect.position.x); + full_descent = MAX(full_descent, E->get().rect.position.x + E->get().rect.size.x); } } } + sd->ascent = full_ascent; + sd->descent = full_descent; } return true; } @@ -869,33 +881,9 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng if (new_sd->orientation == ORIENTATION_HORIZONTAL) { new_sd->objects[key].rect.position.x = new_sd->width; new_sd->width += new_sd->objects[key].rect.size.x; - switch (new_sd->objects[key].inline_align) { - case VALIGN_TOP: { - new_sd->ascent = MAX(new_sd->ascent, new_sd->objects[key].rect.size.y); - } break; - case VALIGN_CENTER: { - new_sd->ascent = MAX(new_sd->ascent, Math::round(new_sd->objects[key].rect.size.y / 2)); - new_sd->descent = MAX(new_sd->descent, Math::round(new_sd->objects[key].rect.size.y / 2)); - } break; - case VALIGN_BOTTOM: { - new_sd->descent = MAX(new_sd->descent, new_sd->objects[key].rect.size.y); - } break; - } } else { new_sd->objects[key].rect.position.y = new_sd->width; new_sd->width += new_sd->objects[key].rect.size.y; - switch (new_sd->objects[key].inline_align) { - case VALIGN_TOP: { - new_sd->ascent = MAX(new_sd->ascent, new_sd->objects[key].rect.size.x); - } break; - case VALIGN_CENTER: { - new_sd->ascent = MAX(new_sd->ascent, Math::round(new_sd->objects[key].rect.size.x / 2)); - new_sd->descent = MAX(new_sd->descent, Math::round(new_sd->objects[key].rect.size.x / 2)); - } break; - case VALIGN_BOTTOM: { - new_sd->descent = MAX(new_sd->descent, new_sd->objects[key].rect.size.x); - } break; - } } } else { const FontDataFallback *fd = font_owner.getornull(gl.font_rid); @@ -923,35 +911,72 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng } } + // Align embedded objects to baseline. + float full_ascent = new_sd->ascent; + float full_descent = new_sd->descent; for (Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = new_sd->objects.front(); E; E = E->next()) { if ((E->get().pos >= new_sd->start) && (E->get().pos < new_sd->end)) { if (sd->orientation == ORIENTATION_HORIZONTAL) { - switch (E->get().inline_align) { - case VALIGN_TOP: { + switch (E->get().inline_align & INLINE_ALIGN_TEXT_MASK) { + case INLINE_ALIGN_TO_TOP: { E->get().rect.position.y = -new_sd->ascent; } break; - case VALIGN_CENTER: { - E->get().rect.position.y = -(E->get().rect.size.y / 2); + case INLINE_ALIGN_TO_CENTER: { + E->get().rect.position.y = (-new_sd->ascent + new_sd->descent) / 2; + } break; + case INLINE_ALIGN_TO_BASELINE: { + E->get().rect.position.y = 0; + } break; + case INLINE_ALIGN_TO_BOTTOM: { + E->get().rect.position.y = new_sd->descent; + } break; + } + switch (E->get().inline_align & INLINE_ALIGN_IMAGE_MASK) { + case INLINE_ALIGN_BOTTOM_TO: { + E->get().rect.position.y -= E->get().rect.size.y; + } break; + case INLINE_ALIGN_CENTER_TO: { + E->get().rect.position.y -= E->get().rect.size.y / 2; } break; - case VALIGN_BOTTOM: { - E->get().rect.position.y = new_sd->descent - E->get().rect.size.y; + case INLINE_ALIGN_TOP_TO: { + //NOP } break; } + full_ascent = MAX(full_ascent, -E->get().rect.position.y); + full_descent = MAX(full_descent, E->get().rect.position.y + E->get().rect.size.y); } else { - switch (E->get().inline_align) { - case VALIGN_TOP: { + switch (E->get().inline_align & INLINE_ALIGN_TEXT_MASK) { + case INLINE_ALIGN_TO_TOP: { E->get().rect.position.x = -new_sd->ascent; } break; - case VALIGN_CENTER: { - E->get().rect.position.x = -(E->get().rect.size.x / 2); + case INLINE_ALIGN_TO_CENTER: { + E->get().rect.position.x = (-new_sd->ascent + new_sd->descent) / 2; + } break; + case INLINE_ALIGN_TO_BASELINE: { + E->get().rect.position.x = 0; + } break; + case INLINE_ALIGN_TO_BOTTOM: { + E->get().rect.position.x = new_sd->descent; + } break; + } + switch (E->get().inline_align & INLINE_ALIGN_IMAGE_MASK) { + case INLINE_ALIGN_BOTTOM_TO: { + E->get().rect.position.x -= E->get().rect.size.x; + } break; + case INLINE_ALIGN_CENTER_TO: { + E->get().rect.position.x -= E->get().rect.size.x / 2; } break; - case VALIGN_BOTTOM: { - E->get().rect.position.x = new_sd->descent - E->get().rect.size.x; + case INLINE_ALIGN_TOP_TO: { + //NOP } break; } + full_ascent = MAX(full_ascent, -E->get().rect.position.x); + full_descent = MAX(full_descent, E->get().rect.position.x + E->get().rect.size.x); } } } + new_sd->ascent = full_ascent; + new_sd->descent = full_descent; } new_sd->valid = true; @@ -1148,7 +1173,7 @@ bool TextServerFallback::shaped_text_update_justification_ops(RID p_shaped) { return true; } -void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint8_t p_clip_flags) { +void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint8_t p_trim_flags) { _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped_line); ERR_FAIL_COND_MSG(!sd, "ShapedTextDataAdvanced invalid."); @@ -1156,13 +1181,22 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl shaped_text_shape(p_shaped_line); } - bool add_ellipsis = (p_clip_flags & OVERRUN_ADD_ELLIPSIS) == OVERRUN_ADD_ELLIPSIS; - bool cut_per_word = (p_clip_flags & OVERRUN_TRIM_WORD_ONLY) == OVERRUN_TRIM_WORD_ONLY; - bool enforce_ellipsis = (p_clip_flags & OVERRUN_ENFORCE_ELLIPSIS) == OVERRUN_ENFORCE_ELLIPSIS; + sd->overrun_trim_data.ellipsis_glyph_buf.clear(); + + bool add_ellipsis = (p_trim_flags & OVERRUN_ADD_ELLIPSIS) == OVERRUN_ADD_ELLIPSIS; + bool cut_per_word = (p_trim_flags & OVERRUN_TRIM_WORD_ONLY) == OVERRUN_TRIM_WORD_ONLY; + bool enforce_ellipsis = (p_trim_flags & OVERRUN_ENFORCE_ELLIPSIS) == OVERRUN_ENFORCE_ELLIPSIS; + bool justification_aware = (p_trim_flags & OVERRUN_JUSTIFICATION_AWARE) == OVERRUN_JUSTIFICATION_AWARE; Glyph *sd_glyphs = sd->glyphs.ptrw(); - if ((p_clip_flags & OVERRUN_TRIM) == OVERRUN_NO_TRIMMING || sd_glyphs == nullptr || p_width <= 0 || !(sd->width > p_width || enforce_ellipsis)) { + if ((p_trim_flags & OVERRUN_TRIM) == OVERRUN_NO_TRIMMING || sd_glyphs == nullptr || p_width <= 0 || !(sd->width > p_width || enforce_ellipsis)) { + sd->overrun_trim_data.trim_pos = -1; + sd->overrun_trim_data.ellipsis_pos = -1; + return; + } + + if (justification_aware && !sd->fit_width_minimum_reached) { return; } @@ -1174,34 +1208,27 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl uint32_t whitespace_gl_idx = font_get_glyph_index(last_gl_font_rid, ' '); Vector2 whitespace_adv = font_get_glyph_advance(last_gl_font_rid, whitespace_gl_idx, last_gl_font_size); - int ellipsis_advance = 0; + int ellipsis_width = 0; if (add_ellipsis) { - ellipsis_advance = 3 * dot_adv.x + font_get_spacing_glyph(last_gl_font_rid) + (cut_per_word ? whitespace_adv.x : 0); + ellipsis_width = 3 * dot_adv.x + font_get_spacing_glyph(last_gl_font_rid) + (cut_per_word ? whitespace_adv.x : 0); } int ell_min_characters = 6; float width = sd->width; - bool is_rtl = sd->direction == DIRECTION_RTL || (sd->direction == DIRECTION_AUTO && sd->para_direction == DIRECTION_RTL); - - int trim_pos = (is_rtl) ? sd_size : 0; + int trim_pos = 0; int ellipsis_pos = (enforce_ellipsis) ? 0 : -1; int last_valid_cut = 0; bool found = false; - int glyphs_from = (is_rtl) ? 0 : sd_size - 1; - int glyphs_to = (is_rtl) ? sd_size - 1 : -1; - int glyphs_delta = (is_rtl) ? +1 : -1; + for (int i = sd_size - 1; i != -1; i--) { + width -= sd_glyphs[i].advance * sd_glyphs[i].repeat; - for (int i = glyphs_from; i != glyphs_to; i += glyphs_delta) { - if (!is_rtl) { - width -= sd_glyphs[i].advance; - } if (sd_glyphs[i].count > 0) { - bool above_min_char_treshold = ((is_rtl) ? sd_size - 1 - i : i) >= ell_min_characters; + bool above_min_char_treshold = (i >= ell_min_characters); - if (width + (((above_min_char_treshold && add_ellipsis) || enforce_ellipsis) ? ellipsis_advance : 0) <= p_width) { + if (width + (((above_min_char_treshold && add_ellipsis) || enforce_ellipsis) ? ellipsis_width : 0) <= p_width) { if (cut_per_word && above_min_char_treshold) { if ((sd_glyphs[i].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT) { last_valid_cut = i; @@ -1214,95 +1241,60 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl if (found) { trim_pos = last_valid_cut; - if (above_min_char_treshold && width - ellipsis_advance <= p_width) { + if (add_ellipsis && (above_min_char_treshold || enforce_ellipsis) && width - ellipsis_width <= p_width) { ellipsis_pos = trim_pos; } break; } } } - if (is_rtl) { - width -= sd_glyphs[i].advance; - } + } + + sd->overrun_trim_data.trim_pos = trim_pos; + sd->overrun_trim_data.ellipsis_pos = ellipsis_pos; + if (trim_pos == 0 && enforce_ellipsis && add_ellipsis) { + sd->overrun_trim_data.ellipsis_pos = 0; } if ((trim_pos >= 0 && sd->width > p_width) || enforce_ellipsis) { - int added_glyphs = 0; if (add_ellipsis && (ellipsis_pos > 0 || enforce_ellipsis)) { // Insert an additional space when cutting word bound for aesthetics. if (cut_per_word && (ellipsis_pos > 0)) { TextServer::Glyph gl; - gl.start = sd_glyphs[ellipsis_pos].start; - gl.end = sd_glyphs[ellipsis_pos].end; gl.count = 1; gl.advance = whitespace_adv.x; gl.index = whitespace_gl_idx; gl.font_rid = last_gl_font_rid; gl.font_size = last_gl_font_size; - gl.flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL | (is_rtl ? GRAPHEME_IS_RTL : 0); + gl.flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL; - // Optimized glyph insertion by replacing a glyph whenever possible. - int glyph_idx = trim_pos + ((is_rtl) ? -added_glyphs : added_glyphs); - if (is_rtl) { - if (glyph_idx < 0) { - sd->glyphs.insert(0, gl); - } else { - sd->glyphs.set(glyph_idx, gl); - } - } else { - if (glyph_idx > (sd_size - 1)) { - sd->glyphs.append(gl); - } else { - sd->glyphs.set(glyph_idx, gl); - } - } - added_glyphs++; + sd->overrun_trim_data.ellipsis_glyph_buf.append(gl); } // Add ellipsis dots. - for (int d = 0; d < 3; d++) { - TextServer::Glyph gl; - gl.start = sd_glyphs[ellipsis_pos].start; - gl.end = sd_glyphs[ellipsis_pos].end; - gl.count = 1; - gl.advance = dot_adv.x; - gl.index = dot_gl_idx; - gl.font_rid = last_gl_font_rid; - gl.font_size = last_gl_font_size; - gl.flags = GRAPHEME_IS_PUNCTUATION | GRAPHEME_IS_VIRTUAL | (is_rtl ? GRAPHEME_IS_RTL : 0); - - // Optimized glyph insertion by replacing a glyph whenever possible. - int glyph_idx = trim_pos + ((is_rtl) ? -added_glyphs : added_glyphs); - if (is_rtl) { - if (glyph_idx < 0) { - sd->glyphs.insert(0, gl); - } else { - sd->glyphs.set(glyph_idx, gl); - } - } else { - if (glyph_idx > (sd_size - 1)) { - sd->glyphs.append(gl); - } else { - sd->glyphs.set(glyph_idx, gl); - } - } - added_glyphs++; - } - } - - // Cut the remaining glyphs off. - if (!is_rtl) { - sd->glyphs.resize(trim_pos + added_glyphs); - } else { - for (int ridx = 0; ridx <= trim_pos - added_glyphs; ridx++) { - sd->glyphs.remove(0); - } + TextServer::Glyph gl; + gl.count = 1; + gl.repeat = 3; + gl.advance = dot_adv.x; + gl.index = dot_gl_idx; + gl.font_rid = last_gl_font_rid; + gl.font_size = last_gl_font_size; + gl.flags = GRAPHEME_IS_PUNCTUATION | GRAPHEME_IS_VIRTUAL; + + sd->overrun_trim_data.ellipsis_glyph_buf.append(gl); } - // Update to correct width. - sd->width = width + ((ellipsis_pos != -1) ? ellipsis_advance : 0); + sd->text_trimmed = true; + sd->width_trimmed = width + ((ellipsis_pos != -1) ? ellipsis_width : 0); } } +TextServer::TrimData TextServerFallback::shaped_text_get_trim_data(RID p_shaped) const { + _THREAD_SAFE_METHOD_ + ShapedTextData *sd = shaped_owner.getornull(p_shaped); + ERR_FAIL_COND_V_MSG(!sd, TrimData(), "ShapedTextDataAdvanced invalid."); + return sd->overrun_trim_data; +} + bool TextServerFallback::shaped_text_shape(RID p_shaped) { _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped); @@ -1336,33 +1328,9 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) { if (sd->orientation == ORIENTATION_HORIZONTAL) { sd->objects[span.embedded_key].rect.position.x = sd->width; sd->width += sd->objects[span.embedded_key].rect.size.x; - switch (sd->objects[span.embedded_key].inline_align) { - case VALIGN_TOP: { - sd->ascent = MAX(sd->ascent, sd->objects[span.embedded_key].rect.size.y); - } break; - case VALIGN_CENTER: { - sd->ascent = MAX(sd->ascent, Math::round(sd->objects[span.embedded_key].rect.size.y / 2)); - sd->descent = MAX(sd->descent, Math::round(sd->objects[span.embedded_key].rect.size.y / 2)); - } break; - case VALIGN_BOTTOM: { - sd->descent = MAX(sd->descent, sd->objects[span.embedded_key].rect.size.y); - } break; - } } else { sd->objects[span.embedded_key].rect.position.y = sd->width; sd->width += sd->objects[span.embedded_key].rect.size.y; - switch (sd->objects[span.embedded_key].inline_align) { - case VALIGN_TOP: { - sd->ascent = MAX(sd->ascent, sd->objects[span.embedded_key].rect.size.x); - } break; - case VALIGN_CENTER: { - sd->ascent = MAX(sd->ascent, Math::round(sd->objects[span.embedded_key].rect.size.x / 2)); - sd->descent = MAX(sd->descent, Math::round(sd->objects[span.embedded_key].rect.size.x / 2)); - } break; - case VALIGN_BOTTOM: { - sd->descent = MAX(sd->descent, sd->objects[span.embedded_key].rect.size.x); - } break; - } } Glyph gl; gl.start = span.start; @@ -1456,34 +1424,69 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) { } // Align embedded objects to baseline. + float full_ascent = sd->ascent; + float full_descent = sd->descent; for (Map<Variant, ShapedTextData::EmbeddedObject>::Element *E = sd->objects.front(); E; E = E->next()) { if (sd->orientation == ORIENTATION_HORIZONTAL) { - switch (E->get().inline_align) { - case VALIGN_TOP: { + switch (E->get().inline_align & INLINE_ALIGN_TEXT_MASK) { + case INLINE_ALIGN_TO_TOP: { E->get().rect.position.y = -sd->ascent; } break; - case VALIGN_CENTER: { - E->get().rect.position.y = -(E->get().rect.size.y / 2); + case INLINE_ALIGN_TO_CENTER: { + E->get().rect.position.y = (-sd->ascent + sd->descent) / 2; + } break; + case INLINE_ALIGN_TO_BASELINE: { + E->get().rect.position.y = 0; } break; - case VALIGN_BOTTOM: { - E->get().rect.position.y = sd->descent - E->get().rect.size.y; + case INLINE_ALIGN_TO_BOTTOM: { + E->get().rect.position.y = sd->descent; } break; } + switch (E->get().inline_align & INLINE_ALIGN_IMAGE_MASK) { + case INLINE_ALIGN_BOTTOM_TO: { + E->get().rect.position.y -= E->get().rect.size.y; + } break; + case INLINE_ALIGN_CENTER_TO: { + E->get().rect.position.y -= E->get().rect.size.y / 2; + } break; + case INLINE_ALIGN_TOP_TO: { + //NOP + } break; + } + full_ascent = MAX(full_ascent, -E->get().rect.position.y); + full_descent = MAX(full_descent, E->get().rect.position.y + E->get().rect.size.y); } else { - switch (E->get().inline_align) { - case VALIGN_TOP: { + switch (E->get().inline_align & INLINE_ALIGN_TEXT_MASK) { + case INLINE_ALIGN_TO_TOP: { E->get().rect.position.x = -sd->ascent; } break; - case VALIGN_CENTER: { - E->get().rect.position.x = -(E->get().rect.size.x / 2); + case INLINE_ALIGN_TO_CENTER: { + E->get().rect.position.x = (-sd->ascent + sd->descent) / 2; + } break; + case INLINE_ALIGN_TO_BASELINE: { + E->get().rect.position.x = 0; } break; - case VALIGN_BOTTOM: { - E->get().rect.position.x = sd->descent - E->get().rect.size.x; + case INLINE_ALIGN_TO_BOTTOM: { + E->get().rect.position.x = sd->descent; } break; } + switch (E->get().inline_align & INLINE_ALIGN_IMAGE_MASK) { + case INLINE_ALIGN_BOTTOM_TO: { + E->get().rect.position.x -= E->get().rect.size.x; + } break; + case INLINE_ALIGN_CENTER_TO: { + E->get().rect.position.x -= E->get().rect.size.x / 2; + } break; + case INLINE_ALIGN_TOP_TO: { + //NOP + } break; + } + full_ascent = MAX(full_ascent, -E->get().rect.position.x); + full_descent = MAX(full_descent, E->get().rect.position.x + E->get().rect.size.x); } } - + sd->ascent = full_ascent; + sd->descent = full_descent; sd->valid = true; return sd->valid; } diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index b70c8f4ec0..d4cab2409a 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -165,8 +165,8 @@ public: virtual bool shaped_text_get_preserve_control(RID p_shaped) const override; virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "") override; - virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, VAlign p_inline_align = VALIGN_CENTER, int p_length = 1) override; - virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, VAlign p_inline_align = VALIGN_CENTER) override; + virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlign p_inline_align = INLINE_ALIGN_CENTER, int p_length = 1) override; + virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlign p_inline_align = INLINE_ALIGN_CENTER) override; virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override; virtual RID shaped_text_get_parent(RID p_shaped) const override; @@ -178,7 +178,8 @@ public: virtual bool shaped_text_update_breaks(RID p_shaped) override; virtual bool shaped_text_update_justification_ops(RID p_shaped) override; - virtual void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_clip_flags) override; + virtual void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_trim_flags) override; + virtual TrimData shaped_text_get_trim_data(RID p_shaped) const override; virtual bool shaped_text_is_ready(RID p_shaped) const override; diff --git a/modules/vhacd/register_types.cpp b/modules/vhacd/register_types.cpp index 3d7aaee921..2b48e94604 100644 --- a/modules/vhacd/register_types.cpp +++ b/modules/vhacd/register_types.cpp @@ -33,7 +33,7 @@ #include "thirdparty/vhacd/public/VHACD.h" static Vector<Vector<Face3>> convex_decompose(const Vector<Face3> &p_faces, int p_max_convex_hulls = -1) { - Vector<float> vertices; + Vector<real_t> vertices; vertices.resize(p_faces.size() * 9); Vector<uint32_t> indices; indices.resize(p_faces.size() * 3); diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h index 3b7ed3dba6..962ea380aa 100644 --- a/modules/visual_script/visual_script_editor.h +++ b/modules/visual_script/visual_script_editor.h @@ -60,7 +60,7 @@ class VisualScriptEditor : public ScriptEditorBase { EDIT_CUT_NODES, EDIT_PASTE_NODES, EDIT_CREATE_FUNCTION, - REFRESH_GRAPH + REFRESH_GRAPH, }; enum PortAction { diff --git a/modules/visual_script/visual_script_property_selector.cpp b/modules/visual_script/visual_script_property_selector.cpp index 8bf1c6cbfa..bacb1947be 100644 --- a/modules/visual_script/visual_script_property_selector.cpp +++ b/modules/visual_script/visual_script_property_selector.cpp @@ -74,6 +74,8 @@ void VisualScriptPropertySelector::_sbox_input(const Ref<InputEvent> &p_ie) { current->select(0); } break; + default: + break; } } } diff --git a/modules/visual_script/visual_script_yield_nodes.cpp b/modules/visual_script/visual_script_yield_nodes.cpp index 9fa49b8a1d..cded1e587c 100644 --- a/modules/visual_script/visual_script_yield_nodes.cpp +++ b/modules/visual_script/visual_script_yield_nodes.cpp @@ -93,7 +93,7 @@ String VisualScriptYield::get_text() const { class VisualScriptNodeInstanceYield : public VisualScriptNodeInstance { public: VisualScriptYield::YieldMode mode; - float wait_time; + double wait_time; virtual int get_working_memory_size() const { return 1; } //yield needs at least 1 //virtual bool is_output_port_unsequenced(int p_idx) const { return false; } @@ -159,7 +159,7 @@ VisualScriptYield::YieldMode VisualScriptYield::get_yield_mode() { return yield_mode; } -void VisualScriptYield::set_wait_time(float p_time) { +void VisualScriptYield::set_wait_time(double p_time) { if (wait_time == p_time) { return; } @@ -167,7 +167,7 @@ void VisualScriptYield::set_wait_time(float p_time) { ports_changed_notify(); } -float VisualScriptYield::get_wait_time() { +double VisualScriptYield::get_wait_time() { return wait_time; } diff --git a/modules/visual_script/visual_script_yield_nodes.h b/modules/visual_script/visual_script_yield_nodes.h index fa596173a6..6005ff30b0 100644 --- a/modules/visual_script/visual_script_yield_nodes.h +++ b/modules/visual_script/visual_script_yield_nodes.h @@ -47,7 +47,7 @@ public: private: YieldMode yield_mode; - float wait_time; + double wait_time; protected: virtual void _validate_property(PropertyInfo &property) const override; @@ -73,8 +73,8 @@ public: void set_yield_mode(YieldMode p_mode); YieldMode get_yield_mode(); - void set_wait_time(float p_time); - float get_wait_time(); + void set_wait_time(double p_time); + double get_wait_time(); virtual VisualScriptNodeInstance *instantiate(VisualScriptInstance *p_instance) override; |
