← Back to team overview

widelands-dev team mailing list archive

Re: [Merge] lp:~widelands-dev/widelands/animation_manager into lp:widelands

 

Review: Approve

I went through the code - mostly minor nits. Feel free to merge when you have addressed them as you see fit and you are happy with the level of testing.

Diff comments:

> 
> === modified file 'data/campaigns/bar02.wmf/scripting/mission_thread.lua'
> --- data/campaigns/bar02.wmf/scripting/mission_thread.lua	2016-10-23 11:31:25 +0000
> +++ data/campaigns/bar02.wmf/scripting/mission_thread.lua	2017-01-04 08:15:34 +0000
> @@ -359,7 +359,7 @@
>  
>     p1:reveal_fields(map:get_field(4,9):region(6))
>  
> -   local pts = scroll_smoothly_to(map:get_field(4,5))
> +   local pts = scroll_to_field(map:get_field(4,5))

Rename this one to prior_center as well.

>  
>     campaign_message_box(story_msg_7)
>  
> 
> === modified file 'src/base/math.h'
> --- src/base/math.h	2016-11-03 07:20:57 +0000
> +++ src/base/math.h	2017-01-04 08:15:34 +0000
> @@ -40,6 +40,11 @@
>  	return val;
>  }
>  
> +// A simple square function.

Is there a specific reason not to use

http://www.cplusplus.com/reference/cmath/pow/

?

> +template <typename T> T sqr(const T& a) {
> +	return a * a;
> +}
> +
>  }  // namespace math
>  
>  #endif  // end of include guard: WL_BASE_MATH_H
> 
> === modified file 'src/scripting/lua_ui.cc'
> --- src/scripting/lua_ui.cc	2016-10-22 18:19:22 +0000
> +++ src/scripting/lua_ui.cc	2017-01-04 08:15:34 +0000
> @@ -73,11 +73,9 @@
>  */
>  const char LuaPanel::className[] = "Panel";
>  const PropertyType<LuaPanel> LuaPanel::Properties[] = {
> -   PROP_RO(LuaPanel, buttons),          PROP_RO(LuaPanel, tabs),
> -   PROP_RO(LuaPanel, windows),          PROP_RW(LuaPanel, mouse_position_x),
> -   PROP_RW(LuaPanel, mouse_position_y), PROP_RW(LuaPanel, position_x),
> -   PROP_RW(LuaPanel, position_y),       PROP_RW(LuaPanel, width),
> -   PROP_RW(LuaPanel, height),           {nullptr, nullptr, nullptr},
> +   PROP_RO(LuaPanel, buttons),    PROP_RO(LuaPanel, tabs),       PROP_RO(LuaPanel, windows),

I think each of these on a new line would be more readable and make future diffs smaller.

> +   PROP_RW(LuaPanel, position_x), PROP_RW(LuaPanel, position_y), PROP_RW(LuaPanel, width),
> +   PROP_RW(LuaPanel, height),     {nullptr, nullptr, nullptr},
>  };
>  const MethodType<LuaPanel> LuaPanel::Methods[] = {
>     METHOD(LuaPanel, get_descendant_position), {nullptr, nullptr},
> @@ -486,12 +454,17 @@
>     METHOD(LuaMapView, start_road_building),
>     METHOD(LuaMapView, abort_road_building),
>     METHOD(LuaMapView, close),
> +   METHOD(LuaMapView, scroll_to_field),
> +   METHOD(LuaMapView, scroll_to_map_pixel),
> +   METHOD(LuaMapView, is_visible),
> +   METHOD(LuaMapView, mouse_to_field),
> +   METHOD(LuaMapView, mouse_to_pixel),
>     {nullptr, nullptr},
>  };
>  const PropertyType<LuaMapView> LuaMapView::Properties[] = {
> -   PROP_RW(LuaMapView, viewpoint_x), PROP_RW(LuaMapView, viewpoint_y),
> -   PROP_RW(LuaMapView, buildhelp),   PROP_RW(LuaMapView, census),
> -   PROP_RW(LuaMapView, statistics),  PROP_RO(LuaMapView, is_building_road),
> +   PROP_RO(LuaMapView, center_map_pixel), PROP_RW(LuaMapView, buildhelp),

Each of these on a new line?

> +   PROP_RW(LuaMapView, census),           PROP_RW(LuaMapView, statistics),
> +   PROP_RO(LuaMapView, is_building_road), PROP_RO(LuaMapView, is_animating),
>     {nullptr, nullptr, nullptr},
>  };
>  
> @@ -674,6 +634,93 @@
>  	return 0;
>  }
>  
> +/* RST
> +   .. method:: scroll_to_map_pixel(x, y)
> +
> +		Starts an animation to center the view on top of the pixel (x, y) in map
> +		pixel space. Use `is_animating` to check if the animation is still going
> +		on.
> +
> +      :arg x: x coordinate of the pixel
> +      :type x: number
> +      :arg y: y coordinate of the pixel
> +      :type y: number
> +*/
> +int LuaMapView::scroll_to_map_pixel(lua_State* L) {
> +	Widelands::Game& game = get_game(L);
> +	// don't move view in replays
> +	if (game.game_controller()->get_game_type() == GameController::GameType::REPLAY) {
> +		return 0;
> +	}
> +
> +	const Vector2f center(luaL_checkdouble(L, 2), luaL_checkdouble(L, 3));
> +	get()->scroll_to_map_pixel(center, MapView::Transition::Smooth);
> +	return 0;
> +}
> +
> +/* RST
> +   .. method:: scroll_to_map_pixel(field)
> +
> +		Starts an animation to center the view on top of the 'field'. Use
> +		`is_animating` to check if the animation is still going on.
> +
> +      :arg field: the field to center on
> +      :type field: :class:`wl.map.Field`
> +*/
> +int LuaMapView::scroll_to_field(lua_State* L) {
> +	get()->scroll_to_field(
> +	   (*get_user_class<LuaMaps::LuaField>(L, 2))->coords(), MapView::Transition::Smooth);
> +	return 0;
> +}
> +
> +/* RST
> +   .. method:: is_visible(field)
> +
> +		Returns `true` if `field` is currently visible in the map view.
> +
> +      :arg field: the field
> +      :type field: :class:`wl.map.Field`
> +*/
> +int LuaMapView::is_visible(lua_State* L) {
> +	lua_pushboolean(
> +	   L, get()->view_area().contains((*get_user_class<LuaMaps::LuaField>(L, 2))->coords()));
> +	return 1;
> +}
> +
> +/* RST
> +   .. method:: mouse_to_pixel(x, y)
> +
> +		Starts a animation to move the mouse onto the pixel (x, y) of this panel.

an animation

> +		Use `is_animating` to check if the animation is still going on.
> +
> +      :arg x: x coordinate of the pixel
> +      :type x: number
> +      :arg y: y coordinate of the pixel
> +      :type y: number
> +*/
> +int LuaMapView::mouse_to_pixel(lua_State* L) {
> +	int x = luaL_checkint32(L, 2);
> +	int y = luaL_checkint32(L, 3);
> +	get()->mouse_to_pixel(Vector2i(x, y), MapView::Transition::Smooth);
> +	return 0;
> +}
> +
> +/* RST
> +   .. method:: mouse_to_field(field)
> +
> +		Starts a animation to move the mouse onto the 'field'. If 'field' is not

an animation

> +		visible on the screen currently, does nothing. Use `is_animating` to
> +		check if the animation is still going on.
> +
> +      :arg field: the field
> +      :type field: :class:`wl.map.Field`
> +*/
> +int LuaMapView::mouse_to_field(lua_State* L) {
> +	get()->mouse_to_field(
> +	   (*get_user_class<LuaMaps::LuaField>(L, 2))->coords(), MapView::Transition::Smooth);
> +	return 0;
> +}
> +
>  /*
>   * C Functions
>   */
> 
> === modified file 'src/wui/mapview.cc'
> --- src/wui/mapview.cc	2016-11-16 10:01:52 +0000
> +++ src/wui/mapview.cc	2017-01-04 08:15:34 +0000
> @@ -34,96 +36,352 @@
>  
>  namespace {
>  
> -// Given 'p' on a torus of dimension ('h', 'h') and 'r' that contains this
> -// point, change 'p' so that r.x < p.x < r.x + r.w and similar for y.
> -// Containing is defined as such that the shortest distance between the center
> -// of 'r' is smaller than (r.w / 2, r.h / 2). If 'p' is NOT contained in 'r'
> -// this method will loop forever.
> -Vector2f move_inside(Vector2f p, const Rectf& r, float w, float h) {
> -	while (p.x < r.x && r.x < r.x + r.w) {
> +// Number of keyframes to generate for a plan. The more points, the smoother
> +// the animation (though we also lineraly interpolate between keyframes) and
> +// the more work.

I don't understand what "the more work" means here.

> +constexpr int kNumKeyFrames = 102;
> +
> +// The maximum zoom to use in moving animations.
> +constexpr float kMaxAnimationZoom = 8.f;
> +
> +// The time used for paning only automated map movement.

... for panning automated map movement only.

> +constexpr float kShortAnimationMs = 500.f;
> +
> +// The time used for zooming and paning automated map movement.

panning

> +constexpr float kLongAnimationMs = 1500.f;
> +
> +// If the difference between the current zoom and the target zoom in an
> +// animation plan is smaller than this value, we will do a pan-only movement.
> +constexpr float kPanOnlyZoomThreshold = 0.25f;
> +
> +// If the target is less than this many screens at the current zoom level away,
> +// we will do a pan-only movement.
> +constexpr float kPanOnlyDistanceThreshold = 2.0f;
> +
> +// Returns the view area, i.e. the currently visible rectangle in map pixel
> +// space for the given 'view'.
> +Rectf get_view_area(const MapView::View& view, const int width, const int height) {
> +	return Rectf(view.viewpoint, width * view.zoom, height * view.zoom);
> +}
> +
> +constexpr float pow2(float t) {

How about http://www.cplusplus.com/reference/cmath/pow/

> +	return t * t;
> +}
> +
> +template <typename T> T mix(float t, const T& a, const T& b) {

What does this function do? Comment.

> +	return a * (1.f - t) + b * t;
> +}
> +
> +// https://en.wikipedia.org/wiki/Smoothstep
> +template <typename T> class SmoothstepInterpolator {
> +public:
> +	SmoothstepInterpolator(const T& start, const T& end, float dt)
> +	   : start_(start), end_(end), dt_(dt) {
> +	}
> +
> +	T value(const float time_ms) const {
> +		const float t = math::clamp(time_ms / dt_, 0.f, 1.f);
> +		return mix(pow2(t) * (3.f - 2.f * t), start_, end_);
> +	}
> +
> +private:
> +	T start_, end_;
> +	float dt_;
> +
> +	DISALLOW_COPY_AND_ASSIGN(SmoothstepInterpolator);
> +};
> +
> +// In the first half smoothly interpolate from 'start' to 'middle', then in the
> +// second half interpolate till 'end'.
> +template <typename T> class DoubleSmoothstepInterpolator {
> +public:
> +	DoubleSmoothstepInterpolator(const T& start, const T& middle, const T& end, float dt)
> +	   : first_(start, middle, dt / 2.f), second_(middle, end, dt / 2.f), dt_(dt) {
> +	}
> +
> +	T value(const float time_ms) const {
> +		const float t = math::clamp(time_ms / dt_, 0.f, 1.f);
> +		if (t < 0.5f) {
> +			return first_.value(t * dt_);
> +		} else {
> +			return second_.value((t - 0.5f) * dt_);
> +		}
> +	}
> +
> +private:
> +	const SmoothstepInterpolator<T> first_, second_;
> +	float dt_;
> +
> +	DISALLOW_COPY_AND_ASSIGN(DoubleSmoothstepInterpolator);
> +};
> +
> +// TODO(sirver): Once c++14 is supported, make this a templated lambda inside
> +// 'plan_map_transition'. For now it is a particularly ugly stand alone
> +// function, but it allows us to parametrize over 'zoom_t' withouth a heap
> +// allocation.
> +template <typename T>
> +void do_plan_map_transition(uint32_t start_time,
> +                            const float duration_ms,
> +                            const SmoothstepInterpolator<Vector2f>& center_point_t,
> +                            const T& zoom_t,
> +                            const int width,
> +                            const int height,
> +                            std::deque<MapView::TimestampedView>* plan) {
> +	for (int i = 1; i < kNumKeyFrames - 2; i++) {
> +		float dt = (duration_ms / kNumKeyFrames) * i;
> +		const float zoom = zoom_t.value(dt);
> +		const Vector2f center_point = center_point_t.value(dt);
> +		const Vector2f viewpoint = center_point - Vector2f(width * zoom / 2.f, height * zoom / 2.f);
> +		plan->push_back(MapView::TimestampedView{
> +		   static_cast<uint32_t>(std::lround(start_time + dt)), MapView::View{viewpoint, zoom}});
> +	}
> +}
> +
> +// Calculates a animation plan from 'start' to 'end'. Employs heuristics to
> +// decide what sort of transitions are taken and how long it takes.
> +std::deque<MapView::TimestampedView> plan_map_transition(const uint32_t start_time,
> +                                                         const Widelands::Map& map,
> +                                                         const MapView::View& start,
> +                                                         const MapView::View& end,
> +                                                         const int width,
> +                                                         const int height) {
> +	const Vector2f start_center = get_view_area(start, width, height).center();
> +	const Vector2f end_center = get_view_area(end, width, height).center();
> +	const Vector2f center_point_change =
> +	   MapviewPixelFunctions::calc_pix_difference(map, end_center, start_center);
> +	const float zoom_change = std::abs(start.zoom - end.zoom);
> +
> +	// Heuristic: How many screens is the target point away from the current
> +	// viewpoint? We use it to decide the zoom out factor and scroll speed.
> +	float num_screens = std::max(std::abs(center_point_change.x) / (width * start.zoom),
> +	                             std::abs(center_point_change.y) / (height * start.zoom));
> +
> +	// Do nothing if we are close.
> +	if (zoom_change < 0.25f && std::abs(center_point_change.x) < 10 &&
> +	    std::abs(center_point_change.y) < 10) {
> +		return {};
> +	}
> +
> +	// If the target is more than a copule screens away or we change the zoom we
> +	// do a sort of jumping animation - zoom out, move and zoom back in.
> +	// Otherwise we we just linearly interpolate the zoom.
> +	const bool jumping_animation =
> +	   num_screens > kPanOnlyDistanceThreshold || zoom_change > kPanOnlyZoomThreshold;
> +	const float duration_ms = jumping_animation ? kLongAnimationMs : kShortAnimationMs;
> +
> +	std::deque<MapView::TimestampedView> plan;
> +	plan.push_back(MapView::TimestampedView{start_time, start});
> +
> +	const SmoothstepInterpolator<Vector2f> center_point_t(
> +	   start_center, start_center + center_point_change, duration_ms);
> +
> +	if (jumping_animation) {
> +		// We jump higher if the distance is farther - but we never zoom in (i.e.
> +		// negative jump) or jump higher than 'kMaxAnimationZoom'.
> +		const float target_zoom = math::clamp(num_screens + start.zoom, end.zoom, kMaxAnimationZoom);
> +		do_plan_map_transition(
> +		   start_time, duration_ms, center_point_t,
> +		   DoubleSmoothstepInterpolator<float>(start.zoom, target_zoom, end.zoom, duration_ms), width,
> +		   height, &plan);
> +	} else {
> +		do_plan_map_transition(start_time, duration_ms, center_point_t,
> +		                       SmoothstepInterpolator<float>(start.zoom, end.zoom, duration_ms),
> +		                       width, height, &plan);
> +	}
> +
> +	// Correct numeric instabilities. We want to land precisely at 'end'.
> +	const Vector2f end_viewpoint = (start_center + center_point_change) -
> +	                               Vector2f(width * end.zoom / 2.f, height * end.zoom / 2.f);
> +	plan.push_back(
> +	   MapView::TimestampedView{static_cast<uint32_t>(std::lround(start_time + duration_ms)),
> +	                            MapView::View{end_viewpoint, end.zoom}});
> +	return plan;
> +}
> +
> +// Plan a animation zoom around 'center' starting at 'start_zoom' and ending at 'end_zoom'.

an animation

> +std::deque<MapView::TimestampedView> plan_zoom_transition(const uint32_t start_time,
> +                                                          const Vector2f& center,
> +                                                          const float start_zoom,
> +                                                          const float end_zoom,
> +                                                          const int width,
> +                                                          const int height) {
> +	const SmoothstepInterpolator<float> zoom_t(start_zoom, end_zoom, kShortAnimationMs);
> +	std::deque<MapView::TimestampedView> plan;
> +	const auto push = [&](const float dt, const float zoom) {
> +		plan.push_back(
> +		   MapView::TimestampedView{static_cast<uint32_t>(std::lround(start_time + dt)),
> +		                            {center - Vector2f(zoom * width, zoom * height) * 0.5f, zoom}});
> +	};
> +
> +	push(0, start_zoom);
> +	for (int i = 1; i < kNumKeyFrames - 2; i++) {
> +		float dt = (kShortAnimationMs / kNumKeyFrames) * i;
> +		push(dt, zoom_t.value(dt));
> +	}
> +	push(kShortAnimationMs, end_zoom);
> +	return plan;
> +}
> +
> +// Plan a mouse movement 'start' and ending at 'target'.
> +std::deque<MapView::TimestampedMouse> plan_mouse_transition(const MapView::TimestampedMouse& start,
> +                                                            const Vector2i& target) {
> +	const SmoothstepInterpolator<Vector2f> mouse_t(
> +	   start.pixel, target.cast<float>(), kShortAnimationMs);
> +	std::deque<MapView::TimestampedMouse> plan;
> +
> +	plan.push_back(start);
> +	for (int i = 1; i < kNumKeyFrames - 2; i++) {
> +		float dt = (kShortAnimationMs / kNumKeyFrames) * i;
> +		plan.push_back(MapView::TimestampedMouse{
> +		   static_cast<uint32_t>(std::lround(start.t + dt)), mouse_t.value(dt)});
> +	}
> +	plan.push_back(MapView::TimestampedMouse{
> +	   static_cast<uint32_t>(std::lround(start.t + kShortAnimationMs)), target.cast<float>()});
> +	return plan;
> +}
> +
> +}  // namespace
> +
> +MapView::ViewArea::ViewArea(const Rectf& init_rect, const Widelands::Map& map)
> +   : rect_(init_rect), map_(map) {
> +}
> +
> +bool MapView::ViewArea::contains(const Widelands::Coords& c) const {
> +	return contains_map_pixel(MapviewPixelFunctions::to_map_pixel_with_normalization(map_, c));
> +}
> +
> +Vector2f MapView::ViewArea::move_inside(const Widelands::Coords& c) const {
> +	// We want to figure out to which pixel 'c' maps inside our rect_. Since
> +	// Wideland's map is a torus, the current 'rect_' could span the origin.
> +	// Without loss of generality we only discuss x - y follows accordingly.
> +	// Depending on the interpretation, the area spanning the origin means:
> +	// 1) either view_area.x + view_area.w < view_area.x - which would be
> +	// surprising to the rest of Widelands.
> +	// 2) map_pixel.x > get_map_end_screen_x(map).
> +	// We move the point by adding or substracting 'get_map_end_screen_x()' such
> +	// that the point is contained inside of 'rect_'. If we now convert to
> +	// panel pixels, we are guaranteed that the pixel we get back is inside the
> +	// screen bounds.
> +	Vector2f p = MapviewPixelFunctions::to_map_pixel_with_normalization(map_, c);
> +	assert(contains_map_pixel(p));

According to the documentation, this will cause an endless loop if the assert is not satisfied. Maybe we should terminate this function with an error log output if the condition isn't met, to prevent Widelands from hanging in Release builds?

> +
> +	const float w = MapviewPixelFunctions::get_map_end_screen_x(map_);
> +	const float h = MapviewPixelFunctions::get_map_end_screen_y(map_);
> +	while (p.x < rect_.x && rect_.x < rect_.x + rect_.w) {
>  		p.x += w;
>  	}
> -	while (p.x > r.x && r.x > r.x + r.w) {
> +	while (p.x > rect_.x && rect_.x > rect_.x + rect_.w) {
>  		p.x -= w;
>  	}
> -	while (p.y < r.y && r.y < r.y + r.y) {
> +	while (p.y < rect_.y && rect_.y < rect_.y + rect_.y) {
>  		p.y += h;
>  	}
> -	while (p.y > r.y && r.y > r.y + r.y) {
> +	while (p.y > rect_.y && rect_.y > rect_.y + rect_.y) {
>  		p.y -= h;
>  	}
>  	return p;
>  }
>  
> -}  // namespace
> +bool MapView::ViewArea::contains_map_pixel(const Vector2f& map_pixel) const {
> +	// We figure out if 'map_pixel' is visible on screen. To do this, we
> +	// calculate the shortest distance to 'view_area.center()' on a torus. If
> +	// the distance is less than 'view_area.w / 2', the point is visible.
> +	const Vector2f view_center = rect_.center();
> +	const Vector2f dist = MapviewPixelFunctions::calc_pix_difference(map_, view_center, map_pixel);
> +
> +	// Check if the point is visible on screen.
> +	return std::abs(dist.x) <= (rect_.w / 2.f) && std::abs(dist.y) <= (rect_.h / 2.f);
> +}
> +
>  
>  MapView::MapView(
>     UI::Panel* parent, int32_t x, int32_t y, uint32_t w, uint32_t h, InteractiveBase& player)
>     : UI::Panel(parent, x, y, w, h),
>       renderer_(new GameRenderer()),
>       intbase_(player),
> -     viewpoint_(0.f, 0.f),
> -     zoom_(1.f),
> +     view_{Vector2f(0.f, 0.f), 1.f},
>       dragging_(false) {
>  }
>  
>  MapView::~MapView() {
>  }
>  
> -Vector2f MapView::get_viewpoint() const {
> -	return viewpoint_;
> -}
> -
>  Vector2f MapView::to_panel(const Vector2f& map_pixel) const {
> -	return MapviewPixelFunctions::map_to_panel(viewpoint_, zoom_, map_pixel);
> -}
> -
> -Vector2f MapView::to_map(const Vector2f& panel_pixel) const {
> -	return MapviewPixelFunctions::panel_to_map(viewpoint_, zoom_, panel_pixel);
> -}
> -
> -/// Moves the mouse cursor so that it is directly above the given node
> -void MapView::warp_mouse_to_node(Widelands::Coords const c) {
> -	// This problem is surprisingly hard: We want to figure out if the
> -	// 'minimap_pixel' is currently visible on screen and if so, what pixel it
> -	// has. Since Wideland's map is a torus, the current 'view_area' could span
> -	// the origin. Without loss of generality we only discuss x - y follows
> -	// accordingly.
> -	// Depending on the interpretation, the area spanning the origin means:
> -	// 1) either view_area.x + view_area.w < view_area.x - which would be surprising to
> -	//    the rest of Widelands.
> -	// 2) map_pixel.x > get_map_end_screen_x(map).
> -	//
> -	// We are dealing with the problem in two steps: first we figure out if
> -	// 'map_pixel' is visible on screen. To do this, we calculate the shortest
> -	// distance to 'view_area.center()' on a torus. If the distance is less than
> -	// 'view_area.w / 2', the point is visible.
> -	// If that is the case, we move the point by adding or substracting
> -	// 'get_map_end_screen_x()' such that the point is contained inside of
> -	// 'view_area'. If we now convert to panel pixels, we are guaranteed that
> -	// the pixel we get back is inside the panel.
> -
> -	const Widelands::Map& map = intbase().egbase().map();
> -	const Vector2f map_pixel = MapviewPixelFunctions::to_map_pixel_with_normalization(map, c);
> -	const Rectf view_area = get_view_area();
> -
> -	const Vector2f view_center = view_area.center();
> -	const int w = MapviewPixelFunctions::get_map_end_screen_x(map);
> -	const int h = MapviewPixelFunctions::get_map_end_screen_y(map);
> -	const Vector2f dist = MapviewPixelFunctions::calc_pix_difference(map, view_center, map_pixel);
> -
> -	// Check if the point is visible on screen.
> -	if (dist.x > view_area.w / 2.f || dist.y > view_area.h / 2.f) {
> -		return;
> -	}
> -	const Vector2f in_panel = to_panel(move_inside(map_pixel, view_area, w, h));
> -	set_mouse_pos(round(in_panel));
> -	track_sel(in_panel);
> +	return MapviewPixelFunctions::map_to_panel(view_.viewpoint, view_.zoom, map_pixel);
> +}
> +
> +Vector2f MapView::to_map(const Vector2i& panel_pixel) const {
> +	return MapviewPixelFunctions::panel_to_map(
> +	   view_.viewpoint, view_.zoom, panel_pixel.cast<float>());
> +}
> +
> +void MapView::mouse_to_field(const Widelands::Coords& c, const Transition& transition) {
> +	const ViewArea area = view_area();
> +	if (!area.contains(c)) {
> +		return;
> +	}
> +	mouse_to_pixel(round(to_panel(area.move_inside(c))), transition);
> +}
> +
> +void MapView::mouse_to_pixel(const Vector2i& pixel, const Transition& transition) {
> +	switch (transition) {
> +	case Transition::Jump:
> +		track_sel(pixel);
> +		set_mouse_pos(pixel);
> +		return;
> +
> +	case Transition::Smooth: {
> +		const TimestampedMouse current = animation_target_mouse();
> +		const auto plan = plan_mouse_transition(current, pixel);
> +		if (!plan.empty()) {
> +			mouse_plans_.push_back(plan);
> +		}
> +		return;
> +	}
> +	}
> +	NEVER_HERE();
>  }
>  
>  void MapView::draw(RenderTarget& dst) {
>  	Widelands::EditorGameBase& egbase = intbase().egbase();
>  
> +	uint32_t now = SDL_GetTicks();
> +	while (!view_plans_.empty()) {
> +		auto& plan = view_plans_.front();
> +		while (plan.size() > 1 && plan[1].t < now) {
> +			plan.pop_front();
> +		}
> +		if (plan.size() == 1) {
> +			view_plans_.pop_front();
> +			continue;
> +		}
> +
> +		// Linearly interpolate between the next and the last.
> +		const float t = (now - plan[0].t) / static_cast<float>(plan[1].t - plan[0].t);
> +		const View new_view = {
> +		   mix(t, plan[0].view.viewpoint, plan[1].view.viewpoint),
> +		   mix(t, plan[0].view.zoom, plan[1].view.zoom),
> +		};
> +		set_view(new_view, Transition::Jump);
> +		break;
> +	}
> +
> +	while (!mouse_plans_.empty()) {
> +		auto& plan = mouse_plans_.front();
> +		while (plan.size() > 1 && plan[1].t < now) {
> +			plan.pop_front();
> +		}
> +		if (plan.size() == 1) {
> +			mouse_plans_.pop_front();
> +			continue;
> +		}
> +
> +		// Linearly interpolate between the next and the last.
> +		const float t = (now - plan[0].t) / static_cast<float>(plan[1].t - plan[0].t);
> +		mouse_to_pixel(round(mix(t, plan[0].pixel, plan[1].pixel)), Transition::Jump);
> +		break;
> +	}
> +
>  	if (upcast(Widelands::Game, game, &egbase)) {
>  		// Bail out if the game isn't actually loaded.
>  		// This fixes a crash with displaying an error dialog during loading.


-- 
https://code.launchpad.net/~widelands-dev/widelands/animation_manager/+merge/313879
Your team Widelands Developers is subscribed to branch lp:~widelands-dev/widelands/animation_manager.


References