From ec4d6e67c1f400d33716d031e46414bc95785ec5 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Wed, 22 Jan 2025 11:50:44 +0100 Subject: [PATCH] Add documentation custom style layers (#3165) --- platform/darwin/src/MLNCustomStyleLayer.h | 91 +++++++++ platform/darwin/src/MLNCustomStyleLayer.mm | 108 +--------- .../ios/MapLibre.docc/BuildingLightExample.md | 2 +- .../MapLibre.docc/CustomStyleLayerExample.md | 192 ++++++++++++++++++ .../ManageOfflineRegionsExample.md | 2 +- platform/ios/MapLibre.docc/MapLibre.md | 4 + .../Sources/CustomStyleLayerExample.swift | 39 ++-- 7 files changed, 320 insertions(+), 118 deletions(-) create mode 100644 platform/ios/MapLibre.docc/CustomStyleLayerExample.md diff --git a/platform/darwin/src/MLNCustomStyleLayer.h b/platform/darwin/src/MLNCustomStyleLayer.h index 666013d14..d0c57a714 100644 --- a/platform/darwin/src/MLNCustomStyleLayer.h +++ b/platform/darwin/src/MLNCustomStyleLayer.h @@ -16,42 +16,133 @@ NS_ASSUME_NONNULL_BEGIN @class MLNMapView; @class MLNStyle; +/// A structure containing context needed to draw a frame in an ``MLNCustomStyleLayer``. typedef struct MLNStyleLayerDrawingContext { + /// The size of the drawable area, in points. CGSize size; + /// The center coordinate of the map view. CLLocationCoordinate2D centerCoordinate; + /// The current zoom level of the map view. double zoomLevel; + /// The heading (direction) in degrees clockwise from true north. CLLocationDirection direction; + /// The current pitch of the map view in degrees, measured from the map plane. CGFloat pitch; + /// The vertical field of view, in degrees, for the map’s perspective. CGFloat fieldOfView; + /// A 4×4 matrix representing the map view’s current projection state. MLNMatrix4 projectionMatrix; } MLNStyleLayerDrawingContext; +/// A style layer that is rendered by Metal code that you provide. +/// +/// By default, this class does nothing. You can subclass it to provide custom +/// Metal drawing code that is run on each frame of the map. +/// +/// You can access an existing ``MLNCustomStyleLayer`` using the +/// ``MLNStyle/layerWithIdentifier:`` method if you know its identifier; +/// otherwise, find it using the ``MLNStyle/layers`` property. You can also +/// create a new ``MLNCustomStyleLayer`` and add it to the style using a method such as +/// ``MLNStyle/addLayer:``. +/// +/// - Warning: This API experimental. It may change +/// at any time without notice. MLN_EXPORT @interface MLNCustomStyleLayer : MLNStyleLayer +/// The style that currently contains the layer. +/// +/// If the layer is not currently part of any style, this property is `nil`. @property (nonatomic, weak, readonly) MLNStyle *style; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" + #if TARGET_OS_IPHONE +/// The OpenGL ES rendering context used for drawing this layer. +/// +/// This property is only valid when using the OpenGL-based rendering backend. +/// If the Metal backend is in use, this property will be unavailable +/// (or `nil`). This property is deprecated and will be removed in a future release. +/// +/// - Warning: Deprecated and will be removed in a future release. @property (nonatomic, readonly) EAGLContext *context; #else +/// The macOS CGL rendering context used for drawing this layer. +/// +/// This property is only valid when using the OpenGL-based rendering backend. +/// If the Metal backend is in use, this property will be `NULL`. +/// This property is deprecated and may be removed in a future release. +/// +/// - Warning: Deprecated and may be removed in a future release. @property (nonatomic, readonly) CGLContextObj context; #endif + #pragma clang diagnostic pop #if MLN_RENDER_BACKEND_METAL +/// The Metal render command encoder used for issuing Metal draw commands. +/// +/// This property is only valid when using the Metal-based rendering backend. +/// If the OpenGL backend is in use, this property will be `nil`. @property (nonatomic, weak) id renderEncoder; #endif +/// Returns an ``MLNCustomStyleLayer`` style layer object initialized with the given identifier. +/// +/// After initializing and configuring the style layer, add it to a map view’s style +/// using the ``MLNStyle/addLayer:`` or +/// ``MLNStyle/insertLayer:belowLayer:`` method. +/// +/// - Parameter identifier: A string that uniquely identifies the layer in the style +/// to which it is added. +/// - Returns: An initialized custom style layer. - (instancetype)initWithIdentifier:(NSString *)identifier; +/// Called immediately after a layer is added to a map view’s style. +/// +/// Override this method in a subclass to perform any setup work before the layer +/// is used to draw a frame. For example, you might compile an OpenGL shader here. +/// The default implementation of this method does nothing. +/// +/// Any resource acquired in this method must be released in +/// ``willMoveFromMapView:``. +/// +/// - Parameter mapView: The map view to whose style the layer has been added. - (void)didMoveToMapView:(MLNMapView *)mapView; +/// Called immediately before a layer is removed from a map view’s style. +/// +/// Override this method in a subclass to perform any teardown work once the +/// layer has drawn its last frame and is about to be removed from the style. +/// The default implementation of this method does nothing. +/// +/// This method may be called even if ``didMoveToMapView:`` has not been called. +/// +/// - Parameter mapView: The map view from whose style the layer is about to be removed. - (void)willMoveFromMapView:(MLNMapView *)mapView; +/// Called each time the layer needs to draw a new frame in a map view. +/// +/// Override this method in a subclass to draw the layer’s content. The default +/// implementation of this method does nothing. +/// +/// Your implementation should not make any assumptions about the OpenGL or Metal +/// state, other than that the current context/encoder is active. You may make +/// changes to the state as needed. You are not required to reset values such as +/// the depth or stencil configuration to their original values. +/// +/// Be sure to draw your fragments with a *z* value of 1 to take advantage of +/// opaque fragment culling, in case the style contains any opaque layers above +/// this layer. +/// +/// - Parameters: +/// - mapView: The map view to which the layer draws. +/// - context: A context structure with information defining the frame to draw. - (void)drawInMapView:(MLNMapView *)mapView withContext:(MLNStyleLayerDrawingContext)context; +/// Forces the map view associated with this style to redraw the receiving layer, +/// causing the ``drawInMapView:withContext:`` method to be called again. - (void)setNeedsDisplay; @end diff --git a/platform/darwin/src/MLNCustomStyleLayer.mm b/platform/darwin/src/MLNCustomStyleLayer.mm index d88ddb020..5e3107bb4 100644 --- a/platform/darwin/src/MLNCustomStyleLayer.mm +++ b/platform/darwin/src/MLNCustomStyleLayer.mm @@ -19,56 +19,19 @@ class MLNCustomLayerHost; -/** - An ``MLNCustomStyleLayer`` is a style layer that is rendered by OpenGL / Metal code that - you provide. - - By default, this class does nothing. You can subclass this class to provide - custom OpenGL or Metal drawing code that is run on each frame of the map. Your subclass - should override the `-didMoveToMapView:`, `-willMoveFromMapView:`, and - `-drawInMapView:withContext:` methods. - - You can access an existing MLNCustomStyleLayer using the - ``MLNStyle/layerWithIdentifier:`` method if you know its identifier; - otherwise, find it using the ``MLNStyle/layers`` property. You can also create a - new MLNCustomStyleLayer and add it to the style using a method such as - ``MLNStyle/addLayer:``. - - @warning This API is undocumented and therefore unsupported. It may change at - any time without notice. - */ @interface MLNCustomStyleLayer () - @property (nonatomic, readonly) mbgl::style::CustomLayer *rawLayer; - @property (nonatomic, readonly, nullable) MLNMapView *mapView; - -/** - The style currently containing the layer. - - If the layer is not currently part of any style, this property is - set to `nil`. - */ @property (nonatomic, weak, readwrite) MLNStyle *style; - @end @implementation MLNCustomStyleLayer -/** - Returns an MLNCustomStyleLayer style layer object initialized with the given identifier. - - After initializing and configuring the style layer, add it to a map view’s - style using the ``MLNStyle/addLayer:`` or - ``MLNStyle/insertLayer:belowLayer:`` method. - - @param identifier A string that uniquely identifies the layer in the style to - which it is added. - @return An initialized OpenGL style layer. - */ - (instancetype)initWithIdentifier:(NSString *)identifier { - auto layer = std::make_unique(identifier.UTF8String, - std::make_unique(self)); + auto layer = std::make_unique( + identifier.UTF8String, + std::make_unique(self) + ); return self = [super initWithPendingLayer:std::move(layer)]; } @@ -93,7 +56,6 @@ class MLNCustomLayerHost; } #endif -// MARK: - Adding to and removing from a map view - (void)addToStyle:(MLNStyle *)style belowLayer:(MLNStyleLayer *)otherLayer { self.style = style; self.style.customLayers[self.identifier] = self; @@ -106,66 +68,15 @@ class MLNCustomLayerHost; self.style = nil; } -/** - Called immediately after a layer is added to a map view’s style. - - This method is intended to be overridden in a subclass. You can use this method - to perform any setup work before the layer is used to draw a frame. For - example, you might use this method to compile an OpenGL shader. The default - implementation of this method does nothing. - - Any resource acquired in this method must be released in - `-willMoveFromMapView:`. - - @param mapView The map view to whose style the layer has been added. - */ - (void)didMoveToMapView:(MLNMapView *)mapView { - } -/** - Called immediately before a layer is removed from a map view’s style. - - This method is intended to be overridden in a subclass. You can use this method - to perform any teardown work once the layer has drawn its last frame and is - about to be removed from the style. The default implementation of this method - does nothing. - - This method may be called even if `-didMoveToMapView:` has not been called. - - @param mapView The map view from whose style the layer is about to be removed. - */ - (void)willMoveFromMapView:(MLNMapView *)mapView { - } -/** - Called each time the layer needs to draw a new frame in a map view. - - This method is intended to be overridden in a subclass. You can use this method - to draw the layer’s content. The default implementation of this method does - nothing. - - Your implementation should not make any assumptions about the OpenGL state, - other than that the current OpenGL context is active. It may make changes to - the OpenGL state. It is not required to reset values such as the depth mask, - stencil mask, or corresponding test flags to their original values. - - Be sure to draw your fragments with a z value of 1 to take advantage - of the opaque fragment culling, in case the style contains any opaque layers - above this layer. - - @param mapView The map view to which the layer draws. - @param context A context structure with information defining the frame to draw. - */ - (void)drawInMapView:(MLNMapView *)mapView withContext:(MLNStyleLayerDrawingContext)context { - } -/** - Forces the map view associated with this style to redraw the receiving layer, - causing the `-drawInMapView:withContext:` method to be called. - */ - (void)setNeedsDisplay { [self.mapView setNeedsRerender]; } @@ -189,10 +100,11 @@ public: } void render(const mbgl::style::CustomLayerRenderParameters& parameters) { - if(!layer) return; + if (!layer) return; #if MLN_RENDER_BACKEND_METAL - MTL::RenderCommandEncoder* ptr = static_cast(parameters).encoder.get(); + MTL::RenderCommandEncoder* ptr = + static_cast(parameters).encoder.get(); id encoder = (__bridge id)ptr; layer.renderEncoder = encoder; #endif @@ -206,12 +118,14 @@ public: .fieldOfView = static_cast(parameters.fieldOfView), .projectionMatrix = MLNMatrix4Make(parameters.projectionMatrix) }; + if (layer.mapView) { [layer drawInMapView:layer.mapView withContext:drawingContext]; } } - void contextLost() {} + void contextLost() { + } void deinitialize() { if (layer == nil) return; @@ -222,6 +136,7 @@ public: layerRef = layer; layer = nil; } + private: __weak MLNCustomStyleLayer * layerRef; MLNCustomStyleLayer * layer = nil; @@ -234,4 +149,3 @@ MLNStyleLayer* CustomStyleLayerPeerFactory::createPeer(style::Layer* rawLayer) { } } // namespace mbgl - diff --git a/platform/ios/MapLibre.docc/BuildingLightExample.md b/platform/ios/MapLibre.docc/BuildingLightExample.md index d5741191d..672bfee18 100644 --- a/platform/ios/MapLibre.docc/BuildingLightExample.md +++ b/platform/ios/MapLibre.docc/BuildingLightExample.md @@ -77,7 +77,7 @@ class BuildingLightExample: UIViewController, MLNMapViewDelegate { } func addFillExtrusionLayer(style: MLNStyle) { - // Access the OpenMapTiles source and use it to create a `MLNFillExtrusionStyleLayer`. The source identifier is `openmaptiles`. Use the `sources` property on a style to verify source identifiers. + // Access the OpenMapTiles source and use it to create a ``MLNFillExtrusionStyleLayer``. The source identifier is `openmaptiles`. Use the `sources` property on a style to verify source identifiers. guard let source = style.source(withIdentifier: "openmaptiles") else { print("Could not find source openmaptiles") return diff --git a/platform/ios/MapLibre.docc/CustomStyleLayerExample.md b/platform/ios/MapLibre.docc/CustomStyleLayerExample.md new file mode 100644 index 000000000..48c7e94eb --- /dev/null +++ b/platform/ios/MapLibre.docc/CustomStyleLayerExample.md @@ -0,0 +1,192 @@ +# Custom Style Layers (Metal API) + +Creating a Custom Style Layer with Metal + +Custom style layers allow you to draw directly with Metal, enabling you to render specialized shapes, custom geometry, or apply advanced visual effects that go beyond what is possible with standard style layers. + +Below you can find an example of how to create a custom style layer with ``MLNCustomStyleLayer``. In this implementation, a SwiftUI view wraps an ``MLNMapView`` and appends a subclassed custom style layer once the map loads. The layer’s ``MLNCustomStyleLayer/didMoveToMapView:`` method handles initialization, including compiling Metal shaders and creating a [`MTLRenderPipelineState`](https://developer.apple.com/documentation/metal/mtlrenderpipelinestate?language=objc) for subsequent draw operations. The ``MLNCustomStyleLayer/willMoveFromMapView:`` method provides a place to release or invalidate resources when the layer is removed from the map, while the ``MLNCustomStyleLayer/drawInMapView:withContext:`` method encodes the drawing commands using a [`MTLRenderCommandEncoder`](https://developer.apple.com/documentation/metal/mtlrendercommandencoder) and the map’s projection matrix. By projecting latitude/longitude coordinates into a normalized 0–1 space and then transforming them into tile coordinates, the layer ensures that rendered geometry aligns correctly with the base map. + +![](CustomStyleLayerExample.png) + + + +```swift +struct CustomStyleLayerExample: UIViewRepresentable { + func makeCoordinator() -> CustomStyleLayerExample.Coordinator { + Coordinator(self) + } + + final class Coordinator: NSObject, MLNMapViewDelegate { + var control: CustomStyleLayerExample + + init(_ control: CustomStyleLayerExample) { + self.control = control + } + + func mapViewDidFinishLoadingMap(_ mapView: MLNMapView) { + let mapOverlay = CustomStyleLayer(identifier: "test-overlay") + let style = mapView.style! + style.layers.append(mapOverlay) + } + } + + func makeUIView(context: Context) -> MLNMapView { + let mapView = MLNMapView() + mapView.delegate = context.coordinator + return mapView + } + + func updateUIView(_: MLNMapView, context _: Context) {} +} + +class CustomStyleLayer: MLNCustomStyleLayer { + private var pipelineState: MTLRenderPipelineState? + private var depthStencilStateWithoutStencil: MTLDepthStencilState? + + override func didMove(to mapView: MLNMapView) { + #if MLN_RENDER_BACKEND_METAL + let resource = mapView.backendResource() + + let shaderSource = """ + #include + using namespace metal; + + typedef struct + { + vector_float2 position; + vector_float4 color; + } Vertex; + + struct RasterizerData + { + float4 position [[position]]; + float4 color; + }; + + struct Uniforms + { + float4x4 matrix; + }; + + vertex RasterizerData + vertexShader(uint vertexID [[vertex_id]], + constant Vertex *vertices [[buffer(0)]], + constant Uniforms &uniforms [[buffer(1)]]) + { + RasterizerData out; + + const float4 position = uniforms.matrix * float4(float2(vertices[vertexID].position.xy), 1, 1); + + out.position = position; + out.color = vertices[vertexID].color; + + return out; + } + + fragment float4 fragmentShader(RasterizerData in [[stage_in]]) + { + return in.color; + } + """ + + var error: NSError? + let device = resource.device + let library = try? device?.makeLibrary(source: shaderSource, options: nil) + assert(library != nil, "Error compiling shaders: \(String(describing: error))") + let vertexFunction = library?.makeFunction(name: "vertexShader") + let fragmentFunction = library?.makeFunction(name: "fragmentShader") + + // Configure a pipeline descriptor that is used to create a pipeline state. + let pipelineStateDescriptor = MTLRenderPipelineDescriptor() + pipelineStateDescriptor.label = "Simple Pipeline" + pipelineStateDescriptor.vertexFunction = vertexFunction + pipelineStateDescriptor.fragmentFunction = fragmentFunction + pipelineStateDescriptor.colorAttachments[0].pixelFormat = resource.mtkView.colorPixelFormat + pipelineStateDescriptor.depthAttachmentPixelFormat = .depth32Float_stencil8 + pipelineStateDescriptor.stencilAttachmentPixelFormat = .depth32Float_stencil8 + + do { + pipelineState = try device?.makeRenderPipelineState(descriptor: pipelineStateDescriptor) + } catch { + assertionFailure("Failed to create pipeline state: \(error)") + } + + // Notice that we don't configure the stencilTest property, leaving stencil testing disabled + let depthStencilDescriptor = MTLDepthStencilDescriptor() + depthStencilDescriptor.depthCompareFunction = .always // Or another value as needed + depthStencilDescriptor.isDepthWriteEnabled = false + + depthStencilStateWithoutStencil = device!.makeDepthStencilState(descriptor: depthStencilDescriptor) + #endif + } + + override func willMove(from _: MLNMapView) {} + + override func draw(in _: MLNMapView, with context: MLNStyleLayerDrawingContext) { + #if MLN_RENDER_BACKEND_METAL + guard let renderEncoder else { return } + + // Project to 0..1. + let p1 = project(CLLocationCoordinate2D(latitude: 25.0, longitude: 12.5)) + let p2 = project(CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0)) + let p3 = project(CLLocationCoordinate2D(latitude: 0.0, longitude: 25.0)) + + // Multiply by the world size so it becomes the tile coordinate system. + let worldSize = 512.0 * pow(2.0, context.zoomLevel) + + let p1Tile = CGPoint(x: p1.x * worldSize, y: p1.y * worldSize) + let p2Tile = CGPoint(x: p2.x * worldSize, y: p2.y * worldSize) + let p3Tile = CGPoint(x: p3.x * worldSize, y: p3.y * worldSize) + + // Then build a triangle from tile coordinates + struct Vertex { var position: vector_float2; var color: vector_float4 } + let triangleVertices: [Vertex] = [ + Vertex(position: vector_float2(Float(p1Tile.x), Float(p1Tile.y)), + color: vector_float4(1, 0, 0, 1)), + Vertex(position: vector_float2(Float(p2Tile.x), Float(p2Tile.y)), + color: vector_float4(0, 1, 0, 1)), + Vertex(position: vector_float2(Float(p3Tile.x), Float(p3Tile.y)), + color: vector_float4(0, 0, 1, 1)), + ] + + // Use the camera's full projection matrix *unchanged*. + var matrix = convertMatrix(context.projectionMatrix) + + // Encode + renderEncoder.setRenderPipelineState(pipelineState!) + renderEncoder.setDepthStencilState(depthStencilStateWithoutStencil) + renderEncoder.setVertexBytes(triangleVertices, length: MemoryLayout.size * triangleVertices.count, index: 0) + renderEncoder.setVertexBytes(&matrix, length: MemoryLayout.size, index: 1) + + // Draw the triangle. + renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3) + #endif + } + + func project(_ coordinate: CLLocationCoordinate2D) -> CGPoint { + // We project the coordinates into the space 0 to 1 and then scale these when drawing based on the current zoom level + let worldSize = 1.0 + let x = (180.0 + coordinate.longitude) / 360.0 * worldSize + let yi = log(tan((45.0 + coordinate.latitude / 2.0) * Double.pi / 180.0)) + let y = (180.0 - yi * (180.0 / Double.pi)) / 360.0 * worldSize + + return CGPoint(x: x, y: y) + } + + struct MLNMatrix4f { + var m00, m01, m02, m03: Float + var m10, m11, m12, m13: Float + var m20, m21, m22, m23: Float + var m30, m31, m32, m33: Float + } + + func convertMatrix(_ mat: MLNMatrix4) -> MLNMatrix4f { + MLNMatrix4f( + m00: Float(mat.m00), m01: Float(mat.m01), m02: Float(mat.m02), m03: Float(mat.m03), + m10: Float(mat.m10), m11: Float(mat.m11), m12: Float(mat.m12), m13: Float(mat.m13), + m20: Float(mat.m20), m21: Float(mat.m21), m22: Float(mat.m22), m23: Float(mat.m23), + m30: Float(mat.m30), m31: Float(mat.m31), m32: Float(mat.m32), m33: Float(mat.m33) + ) + } +} +``` diff --git a/platform/ios/MapLibre.docc/ManageOfflineRegionsExample.md b/platform/ios/MapLibre.docc/ManageOfflineRegionsExample.md index 86e4d7865..1cd20b2ac 100644 --- a/platform/ios/MapLibre.docc/ManageOfflineRegionsExample.md +++ b/platform/ios/MapLibre.docc/ManageOfflineRegionsExample.md @@ -174,7 +174,7 @@ private extension MLNOfflinePackProgress { return 0 } - let percentage = Float((countOfResourcesCompleted / countOfResourcesExpected) * 100) + let percentage = Float(countOfResourcesCompleted) / Float(countOfResourcesExpected) * 100 return percentage } diff --git a/platform/ios/MapLibre.docc/MapLibre.md b/platform/ios/MapLibre.docc/MapLibre.md index 17a823098..72a119db1 100644 --- a/platform/ios/MapLibre.docc/MapLibre.md +++ b/platform/ios/MapLibre.docc/MapLibre.md @@ -49,6 +49,10 @@ Powerful, free and open-source mapping toolkit with full control over data sourc - - +### Advanced + +- + ### Other Articles - diff --git a/platform/ios/app-swift/Sources/CustomStyleLayerExample.swift b/platform/ios/app-swift/Sources/CustomStyleLayerExample.swift index 0d3c3237a..bcf4aaac1 100644 --- a/platform/ios/app-swift/Sources/CustomStyleLayerExample.swift +++ b/platform/ios/app-swift/Sources/CustomStyleLayerExample.swift @@ -118,38 +118,39 @@ class CustomStyleLayer: MLNCustomStyleLayer { override func draw(in _: MLNMapView, with context: MLNStyleLayerDrawingContext) { #if MLN_RENDER_BACKEND_METAL - // Use the supplied render command encoder to encode commands - guard let renderEncoder else { - return - } + guard let renderEncoder else { return } + // Project to 0..1. let p1 = project(CLLocationCoordinate2D(latitude: 25.0, longitude: 12.5)) let p2 = project(CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0)) let p3 = project(CLLocationCoordinate2D(latitude: 0.0, longitude: 25.0)) - struct Vertex { - var position: vector_float2 - var color: vector_float4 - } + // Multiply by the world size so it becomes the tile coordinate system. + let worldSize = 512.0 * pow(2.0, context.zoomLevel) + let p1Tile = CGPoint(x: p1.x * worldSize, y: p1.y * worldSize) + let p2Tile = CGPoint(x: p2.x * worldSize, y: p2.y * worldSize) + let p3Tile = CGPoint(x: p3.x * worldSize, y: p3.y * worldSize) + + // Then build a triangle from tile coordinates + struct Vertex { var position: vector_float2; var color: vector_float4 } let triangleVertices: [Vertex] = [ - Vertex(position: vector_float2(Float(p1.x), Float(p1.y)), color: vector_float4(1, 0, 0, 1)), - Vertex(position: vector_float2(Float(p2.x), Float(p2.y)), color: vector_float4(0, 1, 0, 1)), - Vertex(position: vector_float2(Float(p3.x), Float(p3.y)), color: vector_float4(0, 0, 1, 1)), + Vertex(position: vector_float2(Float(p1Tile.x), Float(p1Tile.y)), + color: vector_float4(1, 0, 0, 1)), + Vertex(position: vector_float2(Float(p2Tile.x), Float(p2Tile.y)), + color: vector_float4(0, 1, 0, 1)), + Vertex(position: vector_float2(Float(p3Tile.x), Float(p3Tile.y)), + color: vector_float4(0, 0, 1, 1)), ] - // Convert the projection matrix to float from double, and scale it to match our projection - var projectionMatrix = convertMatrix(context.projectionMatrix) - let worldSize = 512.0 * pow(2.0, context.zoomLevel) - projectionMatrix.m00 = projectionMatrix.m00 * Float(worldSize) - projectionMatrix.m11 = projectionMatrix.m11 * Float(worldSize) + // Use the camera's full projection matrix *unchanged*. + var matrix = convertMatrix(context.projectionMatrix) + // Encode renderEncoder.setRenderPipelineState(pipelineState!) renderEncoder.setDepthStencilState(depthStencilStateWithoutStencil) - - // Pass in the parameter data. renderEncoder.setVertexBytes(triangleVertices, length: MemoryLayout.size * triangleVertices.count, index: 0) - renderEncoder.setVertexBytes(&projectionMatrix, length: MemoryLayout.size, index: 1) + renderEncoder.setVertexBytes(&matrix, length: MemoryLayout.size, index: 1) // Draw the triangle. renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)