SpriteKit - How to make a perspective scrolling background - swift

I know how to make a endless scrolling background in SpriteKit, but I don't know how to make a perspective background.
Please look at the image below, the left is the origin resource, the right is what I want, and it should scrolling from top to bottom.
As #Simone Pistecchia and #Knight0fDragon said, I tried use SKWarpGeometryGrid
let src = [
float2(0, 0), float2(0.33, 0), float2(0.67, 0), float2(1, 0),
float2(0, 1), float2(0.33, 1), float2(0.67, 1), float2(1, 1)
]
let dst = [
float2(0, 0), float2(0.33, 0), float2(0.67, 0), float2(1, 0),
float2(0.33, 1), float2(0.33, 1), float2(0.67, 1), float2(0.67, 1)
]
let back = childNode(withName: "bgNode") as! SKSpriteNode
back.warpGeometry = SKWarpGeometryGrid(columns: 3, rows: 1, sourcePositions: src, destinationPositions: dst)
The code result is the middle image below, the left is origin resource, the right is what I want.
The result image

Related

flutter: transform path in CustomPaint with Float64List Matrix4

I was trying to rotate some path and did not find a snippet and matrix4 in general is not well documented. So in case someone else has this problem, this is what I ended up with
First of all, here is a nice read about the math behind it and how to use matrix4.
In my demo I used an arrow tip. Since sequence matters, I paint it at (0,0), then I rotate it around the z-axis and finally I move the tip to the requested spot.
Path _getArrow (Offset offset, double alpha) {
Path a = Path();
a.moveTo(ARROWSIZE, - ARROWSIZE);
a.lineTo(0, 0);
a.lineTo(ARROWSIZE, ARROWSIZE);
final translateM = Float64List.fromList([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
offset.dx, offset.dy, 0, 1]
);
final rotateM = Float64List.fromList([
cos(alpha), sin(alpha), 0, 0,
-sin(alpha), cos(alpha), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1]
);
final b = a.transform(rotateM);
final c = b.transform(translateM);
return c;
}

Metal/Swift - First element of vertex buffer has one wrong value

I'm just trying to render a red square using metal, and I'm creating a vertex buffer from an array of Vertex structures that look like this:
struct Vertex {
var position: SIMD3<Float>
var color: SIMD4<Float>
}
This is where I'm rendering the square:
var vertices: [Vertex] = [
Vertex(position: [-0.5, -0.5, 0], color: [1, 0, 0, 1]),
Vertex(position: [-0.5, 0.5, 0], color: [1, 0, 0, 1]),
Vertex(position: [0.5, -0.5, 0], color: [1, 0, 0, 1]),
Vertex(position: [0.5, 0.5, 0], color: [1, 0, 0, 1])
]
var vertexBuffer: MTLBuffer?
func render(using renderCommandEncoder: MTLRenderCommandEncoder) {
if self.vertexBuffer == nil {
self.vertexBuffer = self.device.makeBuffer(
bytes: self.vertices,
length: MemoryLayout<Vertex>.stride * self.vertices.count,
options: []
)
}
if let vertexBuffer = self.vertexBuffer {
renderCommandEncoder.setRenderPipelineState(RenderPipelineStates.defaultState)
renderCommandEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
renderCommandEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: vertexBuffer.length / MemoryLayout<Vertex>.stride)
}
}
This is what my render pipeline state looks like:
let library = device.makeDefaultLibrary()!
let vertexShader = library.makeFunction(name: "basicVertexShader")
let fragmentShader = library.makeFunction(name: "basicFragmentShader")
let renderPipelineDescriptor = MTLRenderPipelineDescriptor()
renderPipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
renderPipelineDescriptor.vertexFunction = vertexShader
renderPipelineDescriptor.fragmentFunction = fragmentShader
renderPipelineDescriptor.sampleCount = 4
let vertexDescriptor = MTLVertexDescriptor()
vertexDescriptor.attributes[0].format = .float3
vertexDescriptor.attributes[0].bufferIndex = 0 // Position
vertexDescriptor.attributes[0].offset = 0
vertexDescriptor.attributes[1].format = .float4
vertexDescriptor.attributes[1].bufferIndex = 0 // Color
vertexDescriptor.attributes[1].offset = MemoryLayout<SIMD3<Float>>.stride
vertexDescriptor.layouts[0].stride = MemoryLayout<Vertex>.stride
renderPipelineDescriptor.vertexDescriptor = vertexDescriptor
self.defaultState = try! device.makeRenderPipelineState(descriptor: renderPipelineDescriptor)
The vertex and fragment shaders just pass through the position and color. For some reason, when this is rendered the first float of the color of the first vertex comes into the vertex shader as an extremely small value, effectively showing black. It only happens for the red value of the first vertex in the array.
Red square with one black vertex
I can see from debugging the GPU frame that the first vertex has a red color component of 5E-41 (essentially 0).
I have no idea why this is the case, it happens some time when the vertices are added to the vertex buffer. I'm guessing it has something to do with my render pipeline vertex descriptor, but I haven't been able to figure out what's wrong. Thanks for any help!
This is, with high likelihood, a duplicate of this question. I'd encourage you to consider the workarounds there, and also to file your own feedback to raise visibility of this bug. - warrenm
Correct, this appears to be a driver bug of some sorts. I fixed it by adding the cpuCacheModeWriteCombined option to makeBuffer and have filed feedback.
self.vertexBuffer = self.device.makeBuffer(
bytes: self.vertices,
length: MemoryLayout<Vertex>.stride * self.vertices.count,
options: [.cpuCacheModeWriteCombined]
)

Does SceneKit rotate(by:around:duration:) works?

I wanted to have all the SCNNodes rotate around an axis (says SCNVector3Zero at center). But when I do this:
for node in nodes {
node.runAction(SCNAction.rotate(by: 3.14, around: SCNVector3Zero, duration: 0.5))
}
Nothing is moved. Changed to SCNAction.move instead of rotate works fine. Any idea?
I am testing in ARKit.
SCNVector3Zero is the zero-length vector (0, 0, 0), it does not
specify a direction or axis.
As an example, to rotate around the z-axis, use
SCNAction.rotate(by: .pi, around: SCNVector3(0, 0, 1), duration: 0.5)

SceneKit – Custom geometry does not show up

I should see 2 yellow triangles, but I see nothing.
class Terrain {
private class func createGeometry () -> SCNGeometry {
let sources = [
SCNGeometrySource(vertices:[
SCNVector3(x: -1.0, y: -1.0, z: 0.0),
SCNVector3(x: -1.0, y: 1.0, z: 0.0),
SCNVector3(x: 1.0, y: 1.0, z: 0.0),
SCNVector3(x: 1.0, y: -1.0, z: 0.0)], count:4),
SCNGeometrySource(normals:[
SCNVector3(x: 0.0, y: 0.0, z: -1.0),
SCNVector3(x: 0.0, y: 0.0, z: -1.0),
SCNVector3(x: 0.0, y: 0.0, z: -1.0),
SCNVector3(x: 0.0, y: 0.0, z: -1.0)], count:4)
]
let elements = [
SCNGeometryElement(indices: [0, 2, 3, 0, 1, 2], primitiveType: .Triangles)
]
let geo = SCNGeometry(sources:sources, elements:elements)
let mat = SCNMaterial()
mat.diffuse.contents = UIColor.yellowColor()
mat.doubleSided = true
geo.materials = [mat]
return geo
}
class func createNode () -> SCNNode {
let node = SCNNode(geometry: createGeometry())
node.name = "Terrain"
node.position = SCNVector3()
return node
}
}
I use it as follows:
let terrain = Terrain.createNode()
sceneView.scene?.rootNode.addChildNode(terrain)
let camera = SCNCamera()
camera.zFar = 10000
self.camera = SCNNode()
self.camera.camera = camera
self.camera.position = SCNVector3(x: -20, y: 15, z: 30)
let constraint = SCNLookAtConstraint(target: terrain)
constraint.gimbalLockEnabled = true
self.camera.constraints = [constraint]
sceneView.scene?.rootNode.addChildNode(self.camera)
I get other nodes with non-custom geometry which I see. What's wrong?
Hal Mueller is quite correct in that the indices involved must be a specified type, but it should be noted that this functionality has changed significantly in recent versions of the Swift language. Notably, SCNGeometryElement(indices:, primitiveType:) now functions perfectly well in Swift 4 and I would advise against using CInt which did not work for me. Instead use one of the standard integer types that conforms to the FixedWidthInteger protocol, i.e. Int32. If you know there's a maximum number of vertices involved in your mesh, use the smallest bit size you can that will encompass all of them.
Example:
let vertices = [
SCNVector3(x: 5, y: 4, z: 0),
SCNVector3(x: -5 , y: 4, z: 0),
SCNVector3(x: -5, y: -5, z: 0),
SCNVector3(x: 5, y: -5, z: 0)
]
let allPrimitives: [Int32] = [0, 1, 2, 0, 2, 3]
let vertexSource = SCNGeometrySource(vertices: vertices)
let element = SCNGeometryElement(indices: allPrimitives, primitiveType: .triangles)
let geometry = SCNGeometry(sources: [vertexSource], elements: [element])
SCNNode(geometry: geometry)
What's Happening Here?
First we create an array of vertices describing points in three-dimensional space. The allPrimitives array describes how those vertices link up. Each element is an index from the vertices array. Since we're using triangles, these should be considered in groups of three, one for each corner. For simplicity's sake I've done a simple flat square here. We then create a geometry source with the semantic type of vertices using the original array of all vertices, and a geometry element using the allPrimitives array, also informing it that they are triangles so it knows to group them in threes. These can then be used to create the SCNGeometry object with which we initialise our SCNNode.
An easy way to think about it is that the vertex source exists only to list all the vertices in the object. The geometry element exists only to describe how those vertices are linked up. It's the SCNGeometry that combines these two objects together to create the final physical representation.
Note: see Ash's answer, which is a much better approach for modern Swift than this one.
Your index array has the wrong size element. It's being inferred as [Int]. You need [CInt].
I broke out your elements setup into:
let indices = [0, 2, 3, 0, 1, 2] // [Int]
print(sizeof(Int)) // 8
print(sizeof(CInt)) // 4
let elements = [
SCNGeometryElement(indices: indices, primitiveType: .Triangles)
]
To get the indices to be packed like the expected C array, declare the type explicitly:
let indices: [CInt] = [0, 2, 3, 0, 1, 2]
Custom SceneKit Geometry in Swift on iOS not working but equivalent Objective C code does goes into more detail, but it's written against Swift 1, so you'll have to do some translation.
SCNGeometryElement(indices:, primitiveType:) doesn't appear to be documented anywhere, although it does appear in the headers.

Cairo Radial Gradient

I'm using a radial gradient in Cairo, but I'm not getting the expected results. The radial gradient I'm getting is much less fuzzy than I'd expect and I can't seem to fiddle with the color stops in order to get the desired results. Here is the code:
cairo_pattern_t *pat;
pat = cairo_pattern_create_radial(100.0, 100.0, 0.0, 100.0, 100.0, 20.0);
cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, 1);
cairo_pattern_add_color_stop_rgba(pat, 1, 0, 0, 0, 0);
Here is an image of what I'm talking about.
The #cairo IRC channel suggested (Thanks Company!) to use cairo_mask() instead of cairo_paint() to draw the gradient. That results in a squared instead of linear progression.
I did the following in lua. Sorry for the language, but it's easier to prototype something. This maps 1:1 to the C API and shouldn't be hard to translate:
cairo = require("lgi").cairo
s = cairo.ImageSurface(cairo.Format.ARGB32, 200, 100)
c = cairo.Context(s)
c:set_source_rgb(1, 1, 1)
c:paint()
p = cairo.Pattern.create_radial(50, 50, 0, 50, 50, 20)
p:add_color_stop_rgba(0, 0, 0, 0, 1)
p:add_color_stop_rgba(1, 0, 0, 0, 0)
c:save()
c:rectangle(0, 0, 100, 100)
c:clip()
c.source = p
c:paint()
c:restore()
p = cairo.Pattern.create_radial(50, 50, 2, 50, 50, 25)
p:add_color_stop_rgba(0, 0, 0, 0, 1)
p:add_color_stop_rgba(1, 0, 0, 0, 0)
c:translate(100, 0)
c:save()
c:rectangle(0, 0, 100, 100)
c:clip()
c.source = p
c:mask(p)
c:restore()
s:write_to_png("test.png")
To me, the second circle (The one that was cairo_mask()'d with a black source) looks a lot more like what you want: