How to Represent Magnitude for Mass in Swift

How to represent magnitude for mass in Swift?

If you want to be able to specify the unit of the mass with a String perhaps you could use String to represent the shorthand name and variables to give you more information about the unit, such as magnitude. Here's an example:

1. MassUnit

enum MassUnit: String {
case Milligrams = "mg"
case Grams = "g"
case Kilos = "kg"
case Tons = "t"

var magnitude: Int {
let mag: Int

switch self {
case .Milligrams: mag = -3
case .Grams : mag = 0
case .Kilos : mag = 3
case .Tons : mag = 6
}

return mag
}


static func ordersOfMagnitudeFrom(unit1: MassUnit, to unit2: MassUnit) -> Int {
return unit1.magnitude - unit2.magnitude
}
}

extension MassUnit: Printable {
var description: String {
return self.rawValue
}
}

2. Then, for storing actual masses you could use a Struct, which could also handle the conversions. For example:

struct Mass {
var value : Double
var unit : MassUnit

static func convertMass(mass: Mass, toUnit unit: MassUnit) -> Mass {
let ordersOfMagnitude = MassUnit.ordersOfMagnitudeFrom(mass.unit, to: unit)

let multipler = pow(10.0, Double(ordersOfMagnitude))

return Mass(value: mass.value * multipler, unit: unit)
}

// Returns an optional Mass because we can't know for sure
// unitString will represent a MassUnit.
static func convertMass(mass: Mass, toUnit unitString: String) -> Mass? {
if let unit = MassUnit(rawValue: unitString) {
return convertMass(mass, toUnit: unit)
}

return nil
}
}

extension Mass {
init?(value: Double, _ unitString: String) {
if let unit = MassUnit(rawValue: unitString) {
self = Mass(value: value, unit: unit)
} else {
return nil
}
}
}

extension Mass : Printable {
var description: String {
return "\(value) \(unit)"
}
}

3. Then you can use the masses and units:

if let mass = Mass(value: 1, "kg"),
let convertedMass = Mass.convertMass(mass, toUnit: "g") {

println("\(mass) converted to \(MassUnit.Grams) equals \(convertedMass)")

// Prints: 1.0 kg converted to g equals 1000.0 g
}

However if you use a unitString that isn't convertible to a MassUnit (either when creating or converting) nil will be returned. For example:

let mass = Mass(value: 1, "NotAUnit") // nil

Swift: maximum readability and extendability in example unit converter project

I already did something very similar, although it doesn't have a strict notion of SI yet.

See https://github.com/fluidsonic/JetPack/tree/master/sources/measurement

How to adhere to protocol method with a Raw type in method argument?

When dealing with your unit conversions, I advise not trying to use Strings to represent the units when converting the way you've got setup above. That's going to make your code complicated with checking the String can be converted to its respective enum every time you want to make a conversion. Also, what if you want to use the a MassUnit/VolumeUnit instead of a String?

I would recommend using the a setup similar to what I've outlined below. It references my previous answer - How to represent magnitude for mass in Swift?

(Note - I've excluded anything to do with the volume because it's basically the same as the implementation for the mass)

I'd make the units like so:

protocol UnitProtocol {
var magnitude: Int { get }

init?(rawValue: String)
}

// Taken from my previous answer.
enum MassUnit: String, UnitProtocol, Printable {
case Milligram = "mg"
case Gram = "g"

var magnitude: Int {
let mag: Int

switch self {
case .Milligram: mag = -3
case .Gram : mag = 0
}

return mag
}

var description: String {
return rawValue
}
}

// Not making this a method requirement of `UnitProtocol` means you've only got to
// write the code once, here, instead of in every enum that conforms to `UnitProtocol`.
func ordersOfMagnitudeFrom<T: UnitProtocol>(unit1: T, to unit2: T) -> Int {
return unit1.magnitude - unit2.magnitude
}

Then I'd make the masses/volumes like so:

protocol UnitConstruct {
typealias UnitType: UnitProtocol
var amount: Double { get }
var unit : UnitType { get }

init(amount: Double, unit: UnitType)
}

struct Mass : UnitConstruct {
let amount: Double
let unit : MassUnit
}

Now for the converting function! Using a global function means you don't need to rewrite the code for every type than conforms to UnitConstruct.

func convert<T: UnitConstruct>(lhs: T, toUnits unit: T.UnitType) -> T {
let x = Double(ordersOfMagnitudeFrom(lhs.unit, to: unit))
return T(amount: lhs.amount * pow(10, x), unit: unit)
}

// This function is for converting to different units using a `String`,
// as asked in the OP.
func convert<T: UnitConstruct>(lhs: T, toUnits unit: String) -> T? {
if let unit = T.UnitType(rawValue: unit) {
return convert(lhs, toUnits: unit)
}

return nil
}

You can then use the previous code like so:

let mass1 = Mass(amount: 1.0, unit: .Gram)
let mass2 = convert(mass1, toUnits: .Milligram) // 1000.0 mg

// Or, converting using Strings:
let right = convert(mass1, toUnits: "mg") // Optional(1000.0 mg)
let wrong = convert(mass1, toUnits: "NotAUnit") // nil

SKPhysicsBody Change Center of Mass

The center of mass can't be way off, it is exact. The problem is you want your physics body to have a non-uniform density. The density of a rigid physics body is uniform throughout. I don't see a way of doing this other than modifying the physics body itself (i.e. Changing the size/shape of the physics body).

Even if you try creating a physics body from the union of multiple physics bodies using SKPhysicsBody(bodies: [body1,body2]) the density will still be uniform because Sprite Kit will still simulate this as just one body.

The only solution I can think of is to fuse 2 bodes together using a joint as shown below.

class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let node1 = SKShapeNode(circleOfRadius: 20)
node1.physicsBody = SKPhysicsBody(circleOfRadius: 20)
node1.position = CGPoint(x: self.size.width/2.0, y: self.size.height/2.0)

let node2 = SKShapeNode(circleOfRadius: 2)
node2.physicsBody = SKPhysicsBody(circleOfRadius: 1)
node2.position = CGPoint(x: self.size.width/2.0, y: self.size.height/2.0+19)
node2.physicsBody!.mass = node1.physicsBody!.mass * 0.2 //Set body2 mass as a ratio of body1 mass.

self.addChild(node1)
self.addChild(node2)

let joint = SKPhysicsJointFixed.jointWithBodyA(node1.physicsBody!, bodyB: node2.physicsBody!, anchor: node1.position)

self.physicsWorld.addJoint(joint)

self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
node1.physicsBody!.applyImpulse(CGVector(dx: -10, dy: 0))
}
}

However this won't fix the special case where the circle lands perfectly centered. You see I apply an impulse to temporarily solve the issue in the code above. A better solution is to check for this special case in the update method.

Sample Image

Note the gif got cut off a little at the bottom.

Calculating Angle of Reach in Scenekit

You are not supposed to "apply a force", you are supposed to set the initial velocity to the V you used in the formula.

If the vector you are returning is a unit vector (has a magnitude of 1), then multiply each component by V and set the velocity of the object directly.

Also, these formula's don't take damping into account (air friction) -- so to hit the target exactly, set your ball's linearDamping and angularDamping to 0.0 (default is 0.1). You will notice more damping on longer trajectories.

NOTE: if you do this, don't use "mass" in the angle calculation

Why Int8.max &+ Int8.max equals to -2?

Swift (and every other programming language I know) uses 2's complement to represent signed integers, rather than sign-and-magnitude as you seem to assume.

In the 2's complement representation, the leftmost 1 does not represent "a negative sign". You can think of it as representing -128, so the Int8 value of -2 would be represented as 1111 1110 (-128 + 64 + 32 + 16 + 8 + 4 + 2).

OTOH, -126 would be represented as 1000 0010 (-128 + 2).



Related Topics



Leave a reply



Submit