Gondwana Game Engine Development (master) - 2.6.1
Gondwana is a cross-platform 2.5D game and rendering engine written in C#/.NET 8. It provides fine-grained control over rendering, timing, and scene composition, with built-in support for parallax, z-ordering, pixel overhang, collision detection, and particle effects. Gondwana targets desktop and web platforms using SkiaSharp for graphics and pluggable audio backends for desktop and browser targets.
Loading...
Searching...
No Matches
View.cs
1using System.Drawing;
4
6
13public sealed class View
14{
15 private SceneLayer? _zoomAnchorLayer;
16 private PointF _zoomAnchorScreenPoint;
17 private PointF _zoomAnchorWorldPoint;
18
25 public Guid Id { get; } = Guid.NewGuid();
26
34 public Camera Camera { get; }
35
44 public Viewport Viewport { get; }
45
50 public int ZOrder { get; set; } = 0;
51
55 public float MinZoom { get; set; } = 0.1f;
56
60 public float MaxZoom { get; set; } = 8f;
61
62 // Presentation-only state owned by EffectsManager. These values never alter
63 // camera, viewport, world, or collision state.
64 internal float EffectOpacity { get; set; } = 1f;
65 internal float EffectReveal { get; set; } = 1f;
66 internal EffectDirection EffectRevealDirection { get; set; } = EffectDirection.FromLeftToRight;
67 internal PointF EffectOffsetFactor { get; set; } = PointF.Empty;
68 internal PointF EffectOffsetPx { get; set; } = PointF.Empty;
69
70 internal bool HasPresentationEffect =>
71 EffectOpacity < 0.9999f
72 || EffectReveal < 0.9999f
73 || Math.Abs(EffectOffsetFactor.X) > 0.0001f
74 || Math.Abs(EffectOffsetFactor.Y) > 0.0001f
75 || Math.Abs(EffectOffsetPx.X) > 0.0001f
76 || Math.Abs(EffectOffsetPx.Y) > 0.0001f;
77
78 internal bool BlocksViewsBelow => !HasPresentationEffect;
79
80 internal View(Camera cam, Viewport vp)
81 {
82 Camera = cam;
83 Viewport = vp;
84 // Let camera clamp against THIS viewport's visible world size.
85 Camera.GetVisibleWorldSizePx = () => Viewport.VisibleWorldSizePx;
86 }
87
110 public void ZoomAroundScreenPoint(SceneLayer layer, PointF screenPoint, float targetZoom, float durationSeconds)
111 {
112 ArgumentNullException.ThrowIfNull(layer);
113
114 targetZoom = Math.Clamp(targetZoom, MinZoom, MaxZoom);
115
116 PointF worldUnderCursor = ScreenPxToWorldPx(layer, screenPoint);
117
118 // Anchored zoom owns camera positioning until the zoom completes. Cancel an
119 // unrelated explicit pan, but leave follow configuration intact so it can
120 // resume on the next update after the anchored zoom finishes.
121 Camera.CancelPan();
122
123 if (durationSeconds <= 0f)
124 {
125 ClearZoomAnchor();
126 Viewport.SnapZoom(targetZoom);
127 SnapCameraToZoomAnchor(layer, screenPoint, worldUnderCursor);
128 return;
129 }
130
131 _zoomAnchorLayer = layer;
132 _zoomAnchorScreenPoint = screenPoint;
133 _zoomAnchorWorldPoint = worldUnderCursor;
134
135 Viewport.ZoomToOverDuration(targetZoom, durationSeconds);
136 }
137
142 internal void Update(float dtSeconds)
143 {
144 if (_zoomAnchorLayer is { } anchorLayer)
145 {
146 // Update zoom first, then derive the one camera position that keeps
147 // the selected world point at the selected screen point.
148 Viewport.UpdateZoom(dtSeconds);
149 SnapCameraToZoomAnchor(
150 anchorLayer,
151 _zoomAnchorScreenPoint,
152 _zoomAnchorWorldPoint);
153
154 if (!Viewport.IsZoomAnimating)
155 ClearZoomAnchor();
156
157 return;
158 }
159
160 Camera.Update(dtSeconds);
161 Viewport.UpdateZoom(dtSeconds);
162 }
163
164 private void SnapCameraToZoomAnchor(
165 SceneLayer layer,
166 PointF screenPoint,
167 PointF worldPoint)
168 {
169 float zoom = Viewport.Zoom > 0f
170 ? Viewport.Zoom
171 : 1f;
172
173 PointF effectOffset = GetEffectOffsetPx(layer);
174
175 float offsetX =
176 Viewport.TargetRectPx.Left
177 + Viewport.ScreenOffsetPx.X
178 + effectOffset.X;
179
180 float offsetY =
181 Viewport.TargetRectPx.Top
182 + Viewport.ScreenOffsetPx.Y
183 + effectOffset.Y;
184
185 float parallax = layer.Parallax;
186 if (Math.Abs(parallax) < 1e-6f)
187 parallax = 1f;
188
189 float localX = screenPoint.X - offsetX;
190 float localY = screenPoint.Y - offsetY;
191
192 // screen = offset + (world - camera*p) * zoom
193 // camera = (world - local/zoom) / p
194 float cameraX =
195 (worldPoint.X - localX / zoom)
196 / parallax;
197
198 float cameraY =
199 (worldPoint.Y - localY / zoom)
200 / parallax;
201
202 Camera.SnapTo(new PointF(cameraX, cameraY));
203 }
204
205 private void ClearZoomAnchor()
206 {
207 _zoomAnchorLayer = null;
208 _zoomAnchorScreenPoint = PointF.Empty;
209 _zoomAnchorWorldPoint = PointF.Empty;
210 }
211
212 #region Coordinate conversion methods
213
233 public PointF ScreenPxToWorldPx(SceneLayer layer, PointF screenPx)
234 {
235 RenderContext? context = GetCurrentRenderContext();
236 float zoom = context?.ViewportZoom
237 ?? (Viewport.Zoom <= 0f ? 1f : Viewport.Zoom);
238 Rectangle targetRect = context?.ViewportTargetRectPx
239 ?? Viewport.TargetRectPx;
240 PointF screenOffset = context?.ViewportScreenOffsetPx
241 ?? Viewport.ScreenOffsetPx;
242 PointF cameraPosition = context?.CameraPositionPx
243 ?? Camera.PositionPx;
244 PointF effectOffset = GetEffectOffsetPx(layer);
245 float offsetX = targetRect.Left + screenOffset.X + effectOffset.X;
246 float offsetY = targetRect.Top + screenOffset.Y + effectOffset.Y;
247 float parallax = layer.Parallax;
248
249 float worldX = cameraPosition.X * parallax
250 + (screenPx.X - offsetX) / zoom;
251
252 float worldY = cameraPosition.Y * parallax
253 + (screenPx.Y - offsetY) / zoom;
254
255 return new PointF(worldX, worldY);
256 }
257
278 public PointF WorldPxToScreenPx(SceneLayer layer, PointF worldPx)
279 {
280 RenderContext? context = GetCurrentRenderContext();
281 float zoom = context?.ViewportZoom
282 ?? (Viewport.Zoom <= 0f ? 1f : Viewport.Zoom);
283 Rectangle targetRect = context?.ViewportTargetRectPx
284 ?? Viewport.TargetRectPx;
285 PointF screenOffset = context?.ViewportScreenOffsetPx
286 ?? Viewport.ScreenOffsetPx;
287 PointF cameraPosition = context?.CameraPositionPx
288 ?? Camera.PositionPx;
289 PointF effectOffset = GetEffectOffsetPx(layer);
290 float offsetX = targetRect.Left + screenOffset.X + effectOffset.X;
291 float offsetY = targetRect.Top + screenOffset.Y + effectOffset.Y;
292 float parallax = layer.Parallax;
293
294 float screenX = offsetX + (worldPx.X - cameraPosition.X * parallax) * zoom;
295 float screenY = offsetY + (worldPx.Y - cameraPosition.Y * parallax) * zoom;
296
297 return new PointF(screenX, screenY);
298 }
299
308 public PointF ScreenPxToGrid(SceneLayer layer, PointF screenPx)
309 {
310 var worldPx = ScreenPxToWorldPx(layer, screenPx);
311 return layer.CoordinateSystem.GetSceneLayerCoordinatesAtPixel(layer, worldPx);
312 }
313
324 public RectangleF WorldRectToScreenRect(SceneLayer layer, RectangleF worldRect)
325 {
326 if (layer is null)
327 throw new ArgumentNullException(nameof(layer));
328
329 RenderContext? context = GetCurrentRenderContext();
330 float zoom = context?.ViewportZoom
331 ?? (Viewport.Zoom <= 0f ? 1f : Viewport.Zoom);
332 Rectangle targetRect = context?.ViewportTargetRectPx
333 ?? Viewport.TargetRectPx;
334 PointF screenOffset = context?.ViewportScreenOffsetPx
335 ?? Viewport.ScreenOffsetPx;
336 PointF cameraPosition = context?.CameraPositionPx
337 ?? Camera.PositionPx;
338
339 PointF effectOffset = GetEffectOffsetPx(layer);
340 float offsetX = targetRect.Left + screenOffset.X + effectOffset.X;
341 float offsetY = targetRect.Top + screenOffset.Y + effectOffset.Y;
342
343 float parallax = layer.Parallax;
344
345 // screen = offset + (world - camera * p) * zoom
346 float localLeft = worldRect.Left - cameraPosition.X * parallax;
347 float localTop = worldRect.Top - cameraPosition.Y * parallax;
348
349 float scaledLeft = localLeft * zoom;
350 float scaledTop = localTop * zoom;
351 float scaledWidth = worldRect.Width * zoom;
352 float scaledHeight = worldRect.Height * zoom;
353
354 float screenLeft = offsetX + scaledLeft;
355 float screenTop = offsetY + scaledTop;
356
357 return new RectangleF(screenLeft, screenTop, scaledWidth, scaledHeight);
358 }
359
371 public RectangleF ScreenRectToWorldRect(SceneLayer layer, RectangleF screenRect)
372 {
373 if (layer is null)
374 throw new ArgumentNullException(nameof(layer));
375
376 RenderContext? context = GetCurrentRenderContext();
377 float zoom = context?.ViewportZoom
378 ?? (Viewport.Zoom <= 0f ? 1f : Viewport.Zoom);
379 Rectangle targetRect = context?.ViewportTargetRectPx
380 ?? Viewport.TargetRectPx;
381 PointF screenOffset = context?.ViewportScreenOffsetPx
382 ?? Viewport.ScreenOffsetPx;
383 PointF cameraPosition = context?.CameraPositionPx
384 ?? Camera.PositionPx;
385
386 PointF effectOffset = GetEffectOffsetPx(layer);
387 float offsetX = targetRect.Left + screenOffset.X + effectOffset.X;
388 float offsetY = targetRect.Top + screenOffset.Y + effectOffset.Y;
389
390 float parallax = layer.Parallax;
391
392 // (screen - offset)
393 float localLeft = screenRect.Left - offsetX;
394 float localTop = screenRect.Top - offsetY;
395
396 // world = camera*parallax + local / zoom
397 float worldLeft = cameraPosition.X * parallax + localLeft / zoom;
398 float worldTop = cameraPosition.Y * parallax + localTop / zoom;
399
400 float worldWidth = screenRect.Width / zoom;
401 float worldHeight = screenRect.Height / zoom;
402
403 return new RectangleF(worldLeft, worldTop, worldWidth, worldHeight);
404 }
405
406 internal PointF GetEffectOffsetPx(SceneLayer? layer)
407 {
408 RenderContext? context = GetCurrentRenderContext();
409 PointF factor = context?.ViewEffectOffsetFactor
410 ?? EffectOffsetFactor;
411 PointF pixels = context?.ViewEffectOffsetPx
412 ?? EffectOffsetPx;
413 Rectangle targetRect = context?.ViewportTargetRectPx
415
416 if (layer is not null)
417 {
418 factor = new PointF(
419 factor.X + layer.EffectOffsetFactor.X,
420 factor.Y + layer.EffectOffsetFactor.Y);
421 pixels = new PointF(
422 pixels.X + layer.EffectOffsetPx.X,
423 pixels.Y + layer.EffectOffsetPx.Y);
424 }
425
426 var wrappedOffset = layer is null ? PointF.Empty : Gondwana.Drawing.WrappedDrawable.ScreenOffset(this, layer);
427 return new PointF(
428 pixels.X + factor.X * targetRect.Width + wrappedOffset.X,
429 pixels.Y + factor.Y * targetRect.Height + wrappedOffset.Y);
430 }
431
432 internal RectangleF GetPresentationBoundsPx()
433 {
434 PointF offset = GetEffectOffsetPx(layer: null);
435 RectangleF viewport = GetRenderViewportTargetRectPx();
436
437 return new RectangleF(
438 viewport.Left + offset.X,
439 viewport.Top + offset.Y,
440 viewport.Width,
441 viewport.Height);
442 }
443
444 internal Rectangle GetRenderViewportTargetRectPx() =>
445 GetCurrentRenderContext()?.ViewportTargetRectPx
447
448 private RenderContext? GetCurrentRenderContext()
449 {
450 RenderContext? context = RenderContext.Current;
451 return context is not null && ReferenceEquals(context.View, this)
452 ? context
453 : null;
454 }
455
456 #endregion Coordinate conversion methods
457}
void SnapTo(PointF worldUpperLeftPx)
Instantly moves the camera to the specified world-space position, interpreted as the upper-left corne...
Definition Camera.cs:92
float MaxZoom
Gets or sets the maximum zoom level allowed for this View.
Definition View.cs:60
PointF ScreenPxToWorldPx(SceneLayer layer, PointF screenPx)
Converts a screen-space pixel position into world-space coordinates for the specified scene layer,...
Definition View.cs:233
PointF WorldPxToScreenPx(SceneLayer layer, PointF worldPx)
Converts a world-space pixel position into screen-space coordinates for this view,...
Definition View.cs:278
float MinZoom
Gets or sets the minimum zoom level allowed for this View.
Definition View.cs:55
PointF ScreenPxToGrid(SceneLayer layer, PointF screenPx)
Converts a point in screen-space into the grid coordinate on the specified SceneLayer by first mappin...
Definition View.cs:308
Guid Id
Gets the unique identifier for this view instance.
Definition View.cs:25
int ZOrder
Controls the draw order of this view relative to other views. Lower values are drawn first (behind); ...
Definition View.cs:50
RectangleF WorldRectToScreenRect(SceneLayer layer, RectangleF worldRect)
Converts a world-space pixel rectangle into a screen-space rectangle for this View,...
Definition View.cs:324
RectangleF ScreenRectToWorldRect(SceneLayer layer, RectangleF screenRect)
Converts a screen-space rectangle (on the logical Backbuffer) into a world-space rectangle for the gi...
Definition View.cs:371
Camera Camera
Gets the camera that controls the world-space position and following behavior for this view....
Definition View.cs:34
Viewport Viewport
Gets the viewport that defines the screen-space rectangle, zoom level, and other rendering parameters...
Definition View.cs:44
void ZoomAroundScreenPoint(SceneLayer layer, PointF screenPoint, float targetZoom, float durationSeconds)
Zooms the view around a screen-space point while keeping the world point beneath that location fixed ...
Definition View.cs:110
A rectangular window on the render target with its own zoom and placement. Viewport never moves the w...
Definition Viewport.cs:10
Rectangle TargetRectPx
Screen-space rectangle (in SCREEN / RenderSurface pixels) where this view is drawn....
Definition Viewport.cs:35
float Zoom
Zoom factor applied to the world when rendering this view. Values greater than 1 zoom in,...
Definition Viewport.cs:67
Represents a single layer within a Scene, containing a grid of tiles and supporting various coordinat...
Definition SceneLayer.cs:40
float Parallax
Gets or sets the parallax scrolling factor for this layer.
EffectDirection
Specifies the direction in which a directional display effect travels.