ThumbWorks / AugmentedSolarSystem

An Augmented reality experience to explore planets in our Solar System
71 stars 25 forks source link

Enhancement - SceneKitExtension / map a lat / lng coordinate to scenekit earth planet of where user is #47

Open johndpope opened 6 years ago

johndpope commented 6 years ago
        let earthNode = PlanetoidGroupNode(planet: Planet.earth)
        earthNode.updatePlanetLocation(earthAA.position())

currently these lines map SwiftAA into the scenekit. however, I don't believe there's any way to get scenekit camera to specific point of latitude / longitude.

this plist contains a bunch of cities with lat / lngs https://github.com/op1000/EarthTravel/blob/master/EarthTravel/Resources/AllCititesToTravel_258.plist


        NSString* filePath = [[NSBundle mainBundle] pathForResource:@"AllCititesToTravel_258" ofType:@"plist"];
        NSDictionary* objectData = [NSDictionary dictionaryWithContentsOfFile:filePath];

        NSArray* arrayList = [objectData valueForKeyPath:@"list"];
        NSMutableArray* arrayCordinate = [[NSMutableArray alloc] init];
        for (NSDictionary* dicInfo in arrayList) {

            NSString* cityName = dicInfo[@"dest_name"];
            float lat = [dicInfo[@"lat"] floatValue];
            float lng = [dicInfo[@"lng"] floatValue];
            NSString* countryCode = [dicInfo objectForKey:@"country_code"];

            // drop some pins
            KGLEarthCoordinate *pin = [KGLEarthCoordinate coordinateWithLatitude:lat
                                                                    andLongitude:lng
                                                                andPinIdentifier:cityName
                                                                  andConturyCode:countryCode];
            [arrayCordinate addObject:pin];
        }
        [self dropPinsAtLocations:arrayCordinate];

-(void)dropPinsAtLocations:(NSArray *)pinArray
{
    // remove any existing pins
    for (SCNNode *node in _currentPins) {
        [node removeFromParentNode];
    }
    _currentPins = [NSMutableArray array];

    // create new pins
    for (KGLEarthCoordinate *coord in pinArray) {
        KGLPinNode *newPin = [KGLPinNode pinAtLatitude:coord.latitude
                                          andLongitude:coord.longitude
                                                 title:coord.pinIdentifier
                                           countryCode:coord.contryCode];
        if (coord.pinIdentifier) {
            newPin.identifier = coord.pinIdentifier;
        }
        [_shadedNode addChildNode:newPin];
        [_currentPins addObject:newPin];
    }
/*!
 * @discussion Convenience method for creating a pin, with internal nodes set up, at a specified location, assuming an Earth at the center of the scene with a radius of 50 units.
 * @param latitude The pin's latitude, in degrees.
 * @param longitude The pin's longitude, in degrees.
 * @return An instance of the KGLPinNode class.
 */

+ (KGLPinNode *)pinAtLatitude:(float)latitude
                 andLongitude:(float)longitude
                        title:(NSString*)title
                  countryCode:(NSString*)countryCode
{
    KGLPinNode *pin = [super node];

    if (pin) {
        pin.latitude = latitude;
        pin.longitude = longitude;
        pin.countryCode = countryCode;
    }

    pin.name = @"pinWrapper";

    SCNBox* pinScene = [SCNBox boxWithWidth:1.0 height:1.0 length:1.0*108.0/159.0 chamferRadius:0];

    //SCNPyramid* pinScene = [SCNPyramid pyramidWithWidth:1.0 height:1.0 length:1.0];
    SCNNode *pinNode = [SCNNode nodeWithGeometry:pinScene];
    NSString* strCountryImagePath = [NSString stringWithFormat: @"icon_%@", countryCode];
    {
        // ambient light
        SCNMaterial *greenMaterial              = [SCNMaterial material];
        greenMaterial.diffuse.contents          = [UIColor clearColor];
        greenMaterial.locksAmbientWithDiffuse   = YES;

        SCNMaterial *redMaterial                = [SCNMaterial material];
        redMaterial.diffuse.contents            = [UIColor clearColor];
        redMaterial.locksAmbientWithDiffuse     = YES;

        SCNMaterial *blueMaterial               = [SCNMaterial material];
        blueMaterial.diffuse.contents           = [UIColor clearColor];
        blueMaterial.locksAmbientWithDiffuse    = YES;

        SCNMaterial *yellowMaterial             = [SCNMaterial material];
        yellowMaterial.diffuse.contents         = [UIColor clearColor];
        yellowMaterial.locksAmbientWithDiffuse  = YES;

        SCNMaterial *purpleMaterial             = [SCNMaterial material];
        purpleMaterial.diffuse.contents         = strCountryImagePath; // 위를 쳐다보는 면
        purpleMaterial.locksAmbientWithDiffuse  = YES;

        SCNMaterial *magentaMaterial            = [SCNMaterial material];
        magentaMaterial.diffuse.contents        = [UIColor clearColor];
        magentaMaterial.locksAmbientWithDiffuse = YES;

        pinScene.materials =  @[greenMaterial,  redMaterial,    blueMaterial,
                           yellowMaterial, purpleMaterial, magentaMaterial];
    }

    // add the pin geometry to the pin node
    [pin addChildNode:pinNode];

    // pins are small, especially from directly above or zoomed out, so wrap a larger rectangular node around the pin
    // this will create a greater touch area
    SCNBox *touchBrick = [SCNBox boxWithWidth:5.0f height:7.5f length:5.0f chamferRadius:0];
    SCNNode *touchNode = [SCNNode nodeWithGeometry:touchBrick];
    touchNode.hidden = YES;
    touchNode.name = @"TouchPin";
    [pin addChildNode:touchNode];

    // position the pin
    // calculate the pin's position along the Y axis of the Earth, based on the given latitude
    float yPos = sinf(DEGREES_TO_RADIANS(latitude)) * 27.8f*ZOOME_RATIO;
    // calculate what the radius of the horizontal circle that cuts through the Earth is at the given Y position
    float localRadius = [KGLEarthCommonMath radiusOfCircleBisectingSphereOfRadius:27.8f*ZOOME_RATIO atHeight:yPos];
    // using the local radius, calculate the X and Z positions of the pin, based on the given longitude
    HorizontalCoords coords = [KGLEarthCommonMath horizontalCoordinatesAtDegrees:longitude ofSphereRadius:localRadius];
    pin.position = SCNVector3Make(-1 * coords.x, yPos, coords.z);

    // rotate the pin so it stands vertically at 90 degrees from the surface of the Earth
    // first, set the pin's euler angles such that it lies flat against the surface of the Earth, given the pin's location
    // the yaw angle positions the pin so it faces out from the surface of the Earth at its location
    float yawAngle = atan2f(-1 * coords.x, coords.z);
    // the pitch angle tilts the pin so it lies on the ground
    float pitchAngle = -1 * DEGREES_TO_RADIANS(latitude) - M_PI_2;
    pin.eulerAngles = SCNVector3Make(pitchAngle, yawAngle, 0);

    // now rotate the pin by 180 degrees vertically, so it stands up
    SCNMatrix4 latRotation = SCNMatrix4MakeRotation(DEGREES_TO_RADIANS(180),1, 0, 0);
    pin.transform = SCNMatrix4Mult(latRotation, pin.transform);

    //==============================
    // label
    //==============================
    SCNText *text = [SCNText textWithString:title extrusionDepth:0.1];

    SCNMaterial *magentaMaterial = [SCNMaterial material];
    magentaMaterial.diffuse.contents = [UtilManager colorWithHexString:@"ec4f30"];
    magentaMaterial.locksAmbientWithDiffuse = YES;
    text.materials = @[magentaMaterial];

    SCNNode *textNode = [SCNNode nodeWithGeometry:text];
    textNode.position = SCNVector3Make(-1+M_PI_2, 0, 0);
    textNode.transform = SCNMatrix4Mult(SCNMatrix4MakeScale(0.05, 0.05, 0.05), textNode.transform);
    [pin addChildNode:textNode];

    return pin;
}
}
rodericj commented 6 years ago

Can we step back for a second? You're trying to get the lat/lng of your current location? Or of a few cities around the world? If the former, why not use CLLocationManager?

You're trying to get the SCNCamera to a specific location. Are you referring to the second target in this project which is SceneKit based? The main target is ARKit which means you'll just move the phone to the position you want.

Just trying to get on the same page here.

johndpope commented 6 years ago

this ticket is simply to position an item from lat lng on earth ccnode. irrespective of CCLocation Manager eg. add dot to Hong Kong.

screen shot 2017-11-17 at 7 31 12 pm

I'll update the other ticket which specifies in more detail the geocentric point of view which will leverage CLCoreLocationManager.

johndpope commented 6 years ago

found this class https://github.com/grevolution/SceneKitEarthTest/blob/eebb68885cefab784cf60ab8d2813db8c8b4f7a4/SceneKitEarthTest/ViewController.swift

but it's going from CGPoint -> lat / lng depending on how earth node is setup - we need to go the other way around.

    func coordinatesFrom(point: CGPoint) -> CLLocation {
        let x = Double(point.x);
        let y = Double(point.y);

        //NewValue = (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
        // lat is 90 to - 90
        // long is -180 to 180

        let lat : CLLocationDegrees = (y * -180) + 90
        let lon : CLLocationDegrees = (x * 360) - 180

        return CLLocation(latitude: lat, longitude: lon)
    }
screen shot 2017-11-17 at 9 29 02 pm
johndpope commented 6 years ago

found this which successfully maps a lat / lng to a scenekit object https://stackoverflow.com/questions/10473852/convert-latitude-and-longitude-to-point-in-3d-space

https://github.com/RPasecky/ARGlobe/blob/master/ARGlobe/coordinateMarker.swift

screen shot 2017-11-17 at 9 44 56 pm

 func convertLLAtoECEF(coordinateData: coordinateData, radius: Double) {
        //Thanks CodingAway https://stackoverflow.com/questions/10473852/convert-latitude-and-longitude-to-point-in-3d-space
        let rad = radius  //6378137.0       //Radius of the Earth (in meters)
        let f = 1.0/298.257223563  // Flattening factor WGS84 Model
        let longRad = (-coordinateData.longitude + 90) * .pi / 180
        let latRad = (coordinateData.latitude) * .pi / 180

        let cosLat = cos(latRad)
        let sinLat = sin(latRad)
        let FF     = pow(1.0 - f, 2)
        let C      = 1 / sqrt(pow(cosLat, 2) + FF * pow(sinLat, 2))
        let S      = C * FF

        self.position.x = Float((rad * C + coordinateData.altitude) * cosLat * cos(longRad))
        self.position.y = Float((rad * S + coordinateData.altitude) * sinLat)
        self.position.z = Float((rad * C + coordinateData.altitude) * cosLat * sin(longRad))

        print("position: \(self.position)")

    }
johndpope commented 6 years ago

so now - I can plugin CoreLocation manager when app starts - and plot user location on scenekit earth. Then to rotate camera angle to that location.

johndpope commented 6 years ago

cleaner code here - credits @dmojdehi https://github.com/johndpope/SwiftGlobe/blob/master/SwiftGlobe/SwiftGlobe.swift#L167

// code to encapsulate individual glow points
// (extend this to get different glow effects)
class GlobeGlowPoint {
    var latitude = 0.0
    var longitude = 0.0

    // the node of this point (must be added to the scene)
    fileprivate var node : SCNNode!

    init(lat: Double, lon: Double) {
        self.latitude = lat
        self.longitude = lon

        self.node = SCNNode(geometry: SCNPlane(width: kGlowPointWidth, height: kGlowPointWidth) )
        self.node.geometry!.firstMaterial!.diffuse.contents = "yellowGlow-32x32.png"
        // appear a little washed out in daylight...
        self.node.geometry!.firstMaterial!.diffuse.intensity = 0.2
        self.node.geometry!.firstMaterial!.emission.contents = "yellowGlow-32x32.png"
        // but brigheter in dark areas
        self.node.geometry!.firstMaterial!.emission.intensity = 0.7
        self.node.castsShadow = false

        // NB: our textures *center* on 0,0, so adjust by 90 degrees
        let adjustedLon = lon + 90

        // convert lat & lon to xyz
        // Note scenekit coordinate space:
        //      Camera looks  down the Z axis (down from +z)
        //      Right is +x, left is -x
        //      Up is +y, down is -y
        let cosLat = cos(lat * Double.pi / 180.0)
        let sinLat = sin(lat * Double.pi / 180.0);
        let cosLon = cos(adjustedLon * Double.pi / 180.0);
        let sinLon = sin(adjustedLon * Double.pi / 180.0);
        let x = kGlowPointAltitude * cosLat * cosLon;
        let y = kGlowPointAltitude * cosLat * sinLon;
        let z = kGlowPointAltitude * sinLat;
        //
        let sceneKitX = -x
        let sceneKitY = z
        let sceneKitZ = y

        //print("convered lat: \(lat) lon: \(lon) to \(sceneKitX),\(sceneKitY),\(sceneKitZ)")

        let pos = SCNVector3(x: sceneKitX, y: sceneKitY, z: sceneKitZ )
        self.node.position = pos

        // and compute the normal pitch, yaw & roll (facing away from the globe)
        //1. Pitch (the x component) is the rotation about the node's x-axis (in radians)
        let pitch = -lat * Double.pi / 180.0
        //2. Yaw   (the y component) is the rotation about the node's y-axis (in radians)
        let yaw = lon * Double.pi / 180.0
        //3. Roll  (the z component) is the rotation about the node's z-axis (in radians)
        let roll = 0.0

        self.node.eulerAngles = SCNVector3(x: pitch, y: yaw, z: roll )

    }

}