I am trying to initlaise a list as part of a constructor argument. But I do not understand what it wants from me
distortion = List(
size = 10,
init = 0
)
The list is of type room entity
var distortion: List<DistortionCacheEntity>? = null
The init part is underlined in red. I dont know what to put in there or how to write it. Could someone help.
The function you are trying to invoke looks like this:
public inline fun <T> List(size: Int, init: (index: Int) -> T): List<T>
So init here is not an Integer but rather a function. Invoke like so:
distortion = List(size = 10) { index : Int ->
//Create object that the list needs to hold
}
var distortion: List<DistortionCacheEntity>? = null
distortion = List(10) { index -> DistortionCacheEntity()}
The simplest way to initialize a list is with kotlin library function listOf(...)
For example,
listOf(1, 2, 3) // [1, 2, 3]
I have the following structure I like to encode.
I'm aware that I can encode a vector with vector() if the size field is directly in front of the vector data. But here the field encoding the vector size is not adjacent.
case class Item(
address: Int,
size: Int,
)
case class Header {
// lots of other fields before
numOfItems: Int,
// lots of other fields after
}
case class Outer(
hdr: Header,
items: Vector[]
)
Decoding of Outer is OK:
Header.numOfItems is read from the bit vector and items is created with vectorOfN(provide(hdr.numOfItems, Item.codec))
Encoding of Outer is the problem:
When encoding I would like to have numOfItem be taken from the items.length.
I'm aware that I could set numOfItems with additional code when the items Vector is updated or with something like a "before encoding callback".
The question is if there is a more elegant solution. To me Header.numOfItems is redundant with Outer.items.length, so ideally only the
Encoder should know about numOfItems.
You could try building a Codec using consume() and start without building the Outer object:
case class OuterExpanded(
fieldBefore: Int, // Field before number of items in the binary encoding
fieldAdter: Int, // Field after number of items in the binary encoding
items: Vector[Item] // Encoded items
)
// Single Item codec
def itemC: Codec[Item] = (int32 :: int32).as[Item]
def outerExpandedC: Codec[OuterExpanded] = (
int32 :: // Field before count
int32.consume( c => // Item count
int32 :: // Field after count
vectorOfN(provide(c), itemC)) // 'consume' (use and forget) the count
(_.tail.head.length) // provide the length when encoding
).as[OuterExpanded]
As defined above, you get the following when encoding: outerExpandedC.encode(OuterExpanded(-1, -1, Vector(Item(1,2), Item(3,4)))) returns
Successful(BitVector(224 bits,
0xffffffff00000002fffffffe00000001000000020000000300000004))
^ ^ ^ ^-------^-> First Item
|-1 | |-2
|Vector length inserted between the two header fields
Afterwards, you can xmap() the Codec[OuterExpanded] to pack the other header fields together into their own object. Ie (adding two conversion methods to Outer and OuterExpanded):
def outerC: Codec[Outer] =
outerExpandedC.xmap(_.toOuter,_.expand)
case class OuterExpanded(fieldBefore: Int, fieldAfter: Int, items: Vector[Item]) {
def toOuter = Outer(Hdr(fieldBefore,fieldAfter), items)
}
case class Outer(header: Hdr, items: Vector[Item]) {
def expand = OuterExpanded(header.beforeField1, header.beforeField1, items)
}
This can probably be adapted to more complex cases, though I'm not entirely familar with shapeless' heterogeneous lists – or HList – and there might be nicer ways to get to the length of the vector rather than calling _.tail.head.length in the example above, especially if you end up with more than one field after the number of encoded values.
Also, the Codec scaladoc is a nice place to discover useful operators
Based on the previous answer I came up with something like the code below.
I used the consume trick form above and an AtomicInteger to hold the size of the vector.
import java.util.concurrent.atomic.AtomicInteger
import scala.Vector
import org.scalatest._
import scodec._
import scodec.Attempt._
import scodec.codecs._
import scodec.bits._
object SomeStructure {
case class Item(
address: Int,
size: Int)
def itemC: Codec[Item] = (int32 :: int32).as[Item]
case class Hdr(
beforeField1: Int,
// vectorSize would be here
afterField1: Int)
// vectorSize is an "in" param when encoding and an "out" param when decoding
def hdrC(vectorSize: AtomicInteger): Codec[Hdr] =
(int32 ::
int32.consume(c => {
vectorSize.set(c);
int32
})((i) => vectorSize.get)).as[Hdr]
case class Outer(
hdr: Hdr,
var items: Vector[Item])
def outerC() = {
// when decoding the length is in this atomic integer
// when encoding it is set before
val c = new AtomicInteger(-1)
(hdrC(c) :: lazily(vectorOfN(provide(c.get), itemC)))
.xmapc(identity)((g) => { c.set(g.tail.head.length); g })
}.as[Outer]
}
import SomeStructure._
class SomeStructureSpec extends FlatSpec with Matchers {
val bv = hex"ffffffff00000002ffffffff00000001000000020000000300000004".bits
val v = Vector(Item(1, 2), Item(3, 4))
val bv2 = hex"ffffffff00000003ffffffff000000010000000200000003000000040000000500000006".bits
val v2 = Vector(Item(1, 2), Item(3, 4), Item(5, 6))
val o = Outer(Hdr(-1, -1), v)
"outerC" should "encode" in {
o.items = v
outerC.encode(o) shouldBe Successful(bv)
o.items = v2
outerC.encode(o) shouldBe Successful(bv2)
}
it should "decode" in {
o.items = v
outerC.decode(bv) shouldBe Successful(DecodeResult(o, BitVector.empty))
o.items = v2
outerC.decode(bv2) shouldBe Successful(DecodeResult(o, BitVector.empty))
}
}
When defining my messages with scodec, I would like to use nested case classes. For example:
case class Foo(x: Int, y: Int)
object Foo {
def baseCodec = uint16 :: uint16
def codec = baseCodec.as[Foo]
}
case class Bar(a: Int, foo: Foo, b: Int)
object Bar {
val baseCodec = uint8 :: Foo.baseCodec :: uint16
val codec = baseCodec.as[Bar]
}
However, when trying to compile this I get the following:
error: Could not prove that shapeless.::[Int,shapeless.::[shapeless.::[Int,shapeless.::[Int,shapeless.HNil]],shapeless.::[Int,shapeless.HNil]]] can be converted to/from Bar.
val codec = baseCodec.as[Bar]
^
Is there a way of doing this? (In my real code, sometimes the nested case class appears at the beginning of the containing class' parameter list, sometimes in the middle and sometimes at the end).
I figured it out. You need to use the Codec, not the HList. This works:
object Bar {
//Foo.codec instead of Foo.baseCodec
val baseCodec = uint8 :: Foo.codec :: uint16
val codec = baseCodec.as[Bar]
}
I have two points (let's call them pointA and pointB) of type SCNVector3. I want to draw a line between them. Seems like it should be easy, but can't find a way to do it.
I see two options, both have issues:
Use a SCNCylinder with a small radius, with length |pointA-pointB| and then position it/rotate it.
Use a custom SCNGeometry but not sure how; would have to define two triangles to form a very thin rectangle perhaps?
It seems like there should be an easier way of doing this, but I can't seem to find one.
Edit: Using the triangle method gives me this for drawing a line between (0,0,0) and (10,10,10):
CGFloat delta = 0.1;
SCNVector3 positions[] = { SCNVector3Make(0,0,0),
SCNVector3Make(10, 10, 10),
SCNVector3Make(0+delta, 0+delta, 0+delta),
SCNVector3Make(10+delta, 10+delta, 10+delta)};
int indicies[] = {
0,2,1,
1,2,3
};
SCNGeometrySource *vertexSource = [SCNGeometrySource geometrySourceWithVertices:positions count:4];
NSData *indexData = [NSData dataWithBytes:indicies length:sizeof(indicies)];
SCNGeometryElement *element = [SCNGeometryElement geometryElementWithData:indexData primitiveType:SCNGeometryPrimitiveTypeTriangles primitiveCount:2 bytesPerIndex:sizeof(int)];
SCNGeometry *line = [SCNGeometry geometryWithSources:#[vertexSource] elements:#[element]];
SCNNode *lineNode = [SCNNode nodeWithGeometry:line];
[root addChildNode:lineNode];
But there are problems: due to the normals, you can only see this line from one side! It's invisible from the other side. Also, if "delta" is too small you can't see the line at all. As it is, it's technically a rectangle, rather than the line I was going for, which might result in small graphical glitches if I want to draw multiple joined up lines.
Here's a simple extension in Swift:
extension SCNGeometry {
class func lineFrom(vector vector1: SCNVector3, toVector vector2: SCNVector3) -> SCNGeometry {
let indices: [Int32] = [0, 1]
let source = SCNGeometrySource(vertices: [vector1, vector2])
let element = SCNGeometryElement(indices: indices, primitiveType: .Line)
return SCNGeometry(sources: [source], elements: [element])
}
}
There are lots of ways to do this.
As noted, your custom geometry approach has some disadvantages. You should be able to correct the problem of it being invisible from one side by giving its material the doubleSided property. You still may have issues with it being two-dimensional, though.
You could also modify your custom geometry to include more triangles, so you get a tube shape with three or more sides instead of a flat rectangle. Or just have two points in your geometry source, and use the SCNGeometryPrimitiveTypeLine geometry element type to have Scene Kit draw a line segment between them. (Though you won't get as much flexibility in rendering styles with line drawing as with shaded polygons.)
You can also use the SCNCylinder approach you mentioned (or any of the other built-in primitive shapes). Remember that geometries are defined in their own local (aka Model) coordinate space, which Scene Kit interprets relative to the coordinate space defined by a node. In other words, you can define a cylinder (or box or capsule or plane or whatever) that's 1.0 units wide in all dimensions, then use the rotation/scale/position or transform of the SCNNode containing that geometry to make it long, thin, and stretching between the two points you want. (Also note that since your line is going to be pretty thin, you can reduce the segmentCounts of whichever built-in geometry you're using, because that much detail won't be visible.)
Yet another option is the SCNShape class that lets you create an extruded 3D object from a 2D Bézier path. Working out the right transform to get a plane connecting two arbitrary points sounds like some fun math, but once you do it you could easily connect your points with any shape of line you choose.
New code for a line from (0, 0, 0) to (10, 10, 10) below.
I'm not sure if it could be improved further.
SCNVector3 positions[] = {
SCNVector3Make(0.0, 0.0, 0.0),
SCNVector3Make(10.0, 10.0, 10.0)
};
int indices[] = {0, 1};
SCNGeometrySource *vertexSource = [SCNGeometrySource geometrySourceWithVertices:positions
count:2];
NSData *indexData = [NSData dataWithBytes:indices
length:sizeof(indices)];
SCNGeometryElement *element = [SCNGeometryElement geometryElementWithData:indexData
primitiveType:SCNGeometryPrimitiveTypeLine
primitiveCount:1
bytesPerIndex:sizeof(int)];
SCNGeometry *line = [SCNGeometry geometryWithSources:#[vertexSource]
elements:#[element]];
SCNNode *lineNode = [SCNNode nodeWithGeometry:line];
[root addChildNode:lineNode];
Here's one solution
class func lineBetweenNodeA(nodeA: SCNNode, nodeB: SCNNode) -> SCNNode {
let positions: [Float32] = [nodeA.position.x, nodeA.position.y, nodeA.position.z, nodeB.position.x, nodeB.position.y, nodeB.position.z]
let positionData = NSData(bytes: positions, length: MemoryLayout<Float32>.size*positions.count)
let indices: [Int32] = [0, 1]
let indexData = NSData(bytes: indices, length: MemoryLayout<Int32>.size * indices.count)
let source = SCNGeometrySource(data: positionData as Data, semantic: SCNGeometrySource.Semantic.vertex, vectorCount: indices.count, usesFloatComponents: true, componentsPerVector: 3, bytesPerComponent: MemoryLayout<Float32>.size, dataOffset: 0, dataStride: MemoryLayout<Float32>.size * 3)
let element = SCNGeometryElement(data: indexData as Data, primitiveType: SCNGeometryPrimitiveType.line, primitiveCount: indices.count, bytesPerIndex: MemoryLayout<Int32>.size)
let line = SCNGeometry(sources: [source], elements: [element])
return SCNNode(geometry: line)
}
if you would like to update the line width or anything related to modifying properties of the drawn line, you'll want to use one of the openGL calls in SceneKit's rendering callback:
func renderer(aRenderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: NSTimeInterval) {
//Makes the lines thicker
glLineWidth(20)
}
Here is a swift5 version:
func lineBetweenNodes(positionA: SCNVector3, positionB: SCNVector3, inScene: SCNScene) -> SCNNode {
let vector = SCNVector3(positionA.x - positionB.x, positionA.y - positionB.y, positionA.z - positionB.z)
let distance = sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z)
let midPosition = SCNVector3 (x:(positionA.x + positionB.x) / 2, y:(positionA.y + positionB.y) / 2, z:(positionA.z + positionB.z) / 2)
let lineGeometry = SCNCylinder()
lineGeometry.radius = 0.05
lineGeometry.height = distance
lineGeometry.radialSegmentCount = 5
lineGeometry.firstMaterial!.diffuse.contents = GREEN
let lineNode = SCNNode(geometry: lineGeometry)
lineNode.position = midPosition
lineNode.look (at: positionB, up: inScene.rootNode.worldUp, localFront: lineNode.worldUp)
return lineNode
}
So inside your ViewController.cs define your vector points and call a Draw function, then on the last line there - it's just rotating it to look at point b.
var a = someVector3;
var b = someOtherVector3;
nfloat cLength = (nfloat)Vector3Helper.DistanceBetweenPoints(a, b);
var cyclinderLine = CreateGeometry.DrawCylinderBetweenPoints(a, b, cLength, 0.05f, 10);
ARView.Scene.RootNode.Add(cyclinderLine);
cyclinderLine.Look(b, ARView.Scene.RootNode.WorldUp, cyclinderLine.WorldUp);
Create a static CreateGeomery class and put this static method in there
public static SCNNode DrawCylinderBetweenPoints(SCNVector3 a,SCNVector3 b, nfloat length, nfloat radius, int radialSegments){
SCNNode cylinderNode;
SCNCylinder cylinder = new SCNCylinder();
cylinder.Radius = radius;
cylinder.Height = length;
cylinder.RadialSegmentCount = radialSegments;
cylinderNode = SCNNode.FromGeometry(cylinder);
cylinderNode.Position = Vector3Helper.GetMidpoint(a,b);
return cylinderNode;
}
you may also want these utility methods in a static helper class
public static double DistanceBetweenPoints(SCNVector3 a, SCNVector3 b)
{
SCNVector3 vector = new SCNVector3(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
return Math.Sqrt(vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z);
}
public static SCNVector3 GetMidpoint(SCNVector3 a, SCNVector3 b){
float x = (a.X + b.X) / 2;
float y = (a.Y + b.Y) / 2;
float z = (a.Z + b.Z) / 2;
return new SCNVector3(x, y, z);
}
For all my Xamarin c# homies out there.
Here's a solution using triangles that works independent of the direction of the line.
It's constructed using the cross product to get points perpendicular to the line. So you'll need a small SCNVector3 extension, but it'll probably come in handy in other cases, too.
private func makeRect(startPoint: SCNVector3, endPoint: SCNVector3, width: Float ) -> SCNGeometry {
let dir = (endPoint - startPoint).normalized()
let perp = dir.cross(SCNNode.localUp) * width / 2
let firstPoint = startPoint + perp
let secondPoint = startPoint - perp
let thirdPoint = endPoint + perp
let fourthPoint = endPoint - perp
let points = [firstPoint, secondPoint, thirdPoint, fourthPoint]
let indices: [UInt16] = [
1,0,2,
1,2,3
]
let geoSource = SCNGeometrySource(vertices: points)
let geoElement = SCNGeometryElement(indices: indices, primitiveType: .triangles)
let geo = SCNGeometry(sources: [geoSource], elements: [geoElement])
geo.firstMaterial?.diffuse.contents = UIColor.blue.cgColor
return geo
}
SCNVector3 extension:
import Foundation
import SceneKit
extension SCNVector3
{
/**
* Returns the length (magnitude) of the vector described by the SCNVector3
*/
func length() -> Float {
return sqrtf(x*x + y*y + z*z)
}
/**
* Normalizes the vector described by the SCNVector3 to length 1.0 and returns
* the result as a new SCNVector3.
*/
func normalized() -> SCNVector3 {
return self / length()
}
/**
* Calculates the cross product between two SCNVector3.
*/
func cross(_ vector: SCNVector3) -> SCNVector3 {
return SCNVector3(y * vector.z - z * vector.y, z * vector.x - x * vector.z, x * vector.y - y * vector.x)
}
}
Swift version
To generate a line in a form of cylinder with a certain position and an orientation, let's implement the SCNGeometry extension with a cylinderLine() class method inside. The toughest part here is a trigonometry (for defining cylinder's direction). Here it is:
import SceneKit
extension SCNGeometry {
class func cylinderLine(from: SCNVector3, to: SCNVector3,
segments: Int = 5) -> SCNNode {
let x1 = from.x; let x2 = to.x
let y1 = from.y; let y2 = to.y
let z1 = from.z; let z2 = to.z
let subExpr01 = Float((x2-x1) * (x2-x1))
let subExpr02 = Float((y2-y1) * (y2-y1))
let subExpr03 = Float((z2-z1) * (z2-z1))
let distance = CGFloat(sqrtf(subExpr01 + subExpr02 + subExpr03))
let cylinder = SCNCylinder(radius: 0.005, height: CGFloat(distance))
cylinder.radialSegmentCount = segments
cylinder.firstMaterial?.diffuse.contents = NSColor.systemYellow
let lineNode = SCNNode(geometry: cylinder)
lineNode.position = SCNVector3((x1+x2)/2, (y1+y2)/2, (z1+z2)/2)
lineNode.eulerAngles = SCNVector3(x: CGFloat.pi / 2,
y: acos((to.z-from.z)/CGFloat(distance)),
z: atan2((to.y-from.y), (to.x-from.x)))
return lineNode
}
}
The rest is easy.
class ViewController: NSViewController {
#IBOutlet var sceneView: SCNView!
let scene = SCNScene()
var startingPoint: SCNVector3!
var endingPoint: SCNVector3!
override func viewDidLoad() {
super.viewDidLoad()
sceneView.scene = scene
sceneView.backgroundColor = NSColor.black
sceneView.allowsCameraControl = true
self.startingPoint = SCNVector3Zero
self.endingPoint = SCNVector3(1,1,1)
self.lineInBetween()
}
func lineInBetween() {
self.addSphereDot(position: startingPoint)
self.addSphereDot(position: endingPoint)
self.addLine(start: startingPoint, end: endingPoint)
}
func addSphereDot(position: SCNVector3) {
let sphere = SCNSphere(radius: 0.03)
sphere.firstMaterial?.diffuse.contents = NSColor.red
let node = SCNNode(geometry: sphere)
node.position = position
scene.rootNode.addChildNode(node)
}
func addLine(start: SCNVector3, end: SCNVector3) {
let lineNode = SCNGeometry.cylinderLine(from: start, to: end)
scene.rootNode.addChildNode(lineNode)
}
}