mirror of
https://github.com/godotengine/godot.git
synced 2024-11-26 06:03:38 +00:00
Merge pull request #45252 from naithar/feature/plugins-migration
[4.0] [iOS] iOS Plugins Migration
This commit is contained in:
commit
de9c9007c2
@ -45,18 +45,6 @@ for name, path in env.module_list.items():
|
||||
else:
|
||||
SConscript(path + "/SCsub") # Custom.
|
||||
|
||||
# Some modules are not linked automatically but can be enabled optionally
|
||||
# on iOS, so we handle those specially.
|
||||
if env["platform"] == "iphone" and name in [
|
||||
"arkit",
|
||||
"camera",
|
||||
"camera_iphone",
|
||||
"gamecenter",
|
||||
"inappstore",
|
||||
"icloud",
|
||||
]:
|
||||
continue
|
||||
|
||||
lib = env_modules.add_library("module_%s" % name, env.modules_sources)
|
||||
env.Prepend(LIBS=[lib])
|
||||
if env["vsproj"]:
|
||||
|
@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_arkit = env_modules.Clone()
|
||||
|
||||
# (iOS) Enable module support
|
||||
env_arkit.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
|
||||
|
||||
# (iOS) Build as separate static library
|
||||
modules_sources = []
|
||||
env_arkit.add_source_files(modules_sources, "*.cpp")
|
||||
env_arkit.add_source_files(modules_sources, "*.mm")
|
||||
mod_lib = env_modules.add_library("#bin/libgodot_arkit_module" + env["LIBSUFFIX"], modules_sources)
|
@ -1,18 +0,0 @@
|
||||
[config]
|
||||
name="ARKit"
|
||||
binary="arkit_lib.a"
|
||||
|
||||
initialization="register_arkit_types"
|
||||
deinitialization="unregister_arkit_types"
|
||||
|
||||
[dependencies]
|
||||
linked=[]
|
||||
embedded=[]
|
||||
system=["AVFoundation.framework", "ARKit.framework"]
|
||||
|
||||
capabilities=["arkit"]
|
||||
|
||||
files=[]
|
||||
|
||||
[plist]
|
||||
NSCameraUsageDescription="Device camera is used for some functionality"
|
@ -1,134 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* arkit_interface.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef ARKIT_INTERFACE_H
|
||||
#define ARKIT_INTERFACE_H
|
||||
|
||||
#include "servers/camera/camera_feed.h"
|
||||
#include "servers/xr/xr_interface.h"
|
||||
#include "servers/xr/xr_positional_tracker.h"
|
||||
|
||||
/**
|
||||
@author Bastiaan Olij <mux213@gmail.com>
|
||||
|
||||
ARKit interface between iPhone and Godot
|
||||
*/
|
||||
|
||||
// forward declaration for some needed objects
|
||||
class ARKitShader;
|
||||
|
||||
#ifdef __OBJC__
|
||||
|
||||
typedef ARAnchor GodotARAnchor;
|
||||
|
||||
#else
|
||||
|
||||
typedef void GodotARAnchor;
|
||||
#endif
|
||||
|
||||
class ARKitInterface : public XRInterface {
|
||||
GDCLASS(ARKitInterface, XRInterface);
|
||||
|
||||
private:
|
||||
bool initialized;
|
||||
bool session_was_started;
|
||||
bool plane_detection_is_enabled;
|
||||
bool light_estimation_is_enabled;
|
||||
real_t ambient_intensity;
|
||||
real_t ambient_color_temperature;
|
||||
|
||||
Transform transform;
|
||||
CameraMatrix projection;
|
||||
float eye_height, z_near, z_far;
|
||||
|
||||
Ref<CameraFeed> feed;
|
||||
size_t image_width[2];
|
||||
size_t image_height[2];
|
||||
Vector<uint8_t> img_data[2];
|
||||
|
||||
struct anchor_map {
|
||||
XRPositionalTracker *tracker;
|
||||
unsigned char uuid[16];
|
||||
};
|
||||
|
||||
///@TODO should use memory map object from Godot?
|
||||
unsigned int num_anchors;
|
||||
unsigned int max_anchors;
|
||||
anchor_map *anchors;
|
||||
XRPositionalTracker *get_anchor_for_uuid(const unsigned char *p_uuid);
|
||||
void remove_anchor_for_uuid(const unsigned char *p_uuid);
|
||||
void remove_all_anchors();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void start_session();
|
||||
void stop_session();
|
||||
|
||||
bool get_anchor_detection_is_enabled() const override;
|
||||
void set_anchor_detection_is_enabled(bool p_enable) override;
|
||||
virtual int get_camera_feed_id() override;
|
||||
|
||||
bool get_light_estimation_is_enabled() const;
|
||||
void set_light_estimation_is_enabled(bool p_enable);
|
||||
|
||||
real_t get_ambient_intensity() const;
|
||||
real_t get_ambient_color_temperature() const;
|
||||
|
||||
/* while Godot has its own raycast logic this takes ARKits camera into account and hits on any ARAnchor */
|
||||
Array raycast(Vector2 p_screen_coord);
|
||||
|
||||
virtual void notification(int p_what) override;
|
||||
|
||||
virtual StringName get_name() const override;
|
||||
virtual int get_capabilities() const override;
|
||||
|
||||
virtual bool is_initialized() const override;
|
||||
virtual bool initialize() override;
|
||||
virtual void uninitialize() override;
|
||||
|
||||
virtual Size2 get_render_targetsize() override;
|
||||
virtual bool is_stereo() override;
|
||||
virtual Transform get_transform_for_eye(XRInterface::Eyes p_eye, const Transform &p_cam_transform) override;
|
||||
virtual CameraMatrix get_projection_for_eye(XRInterface::Eyes p_eye, real_t p_aspect, real_t p_z_near, real_t p_z_far) override;
|
||||
virtual void commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect) override;
|
||||
|
||||
virtual void process() override;
|
||||
|
||||
// called by delegate (void * because C++ and Obj-C don't always mix, should really change all platform/iphone/*.cpp files to .mm)
|
||||
void _add_or_update_anchor(GodotARAnchor *p_anchor);
|
||||
void _remove_anchor(GodotARAnchor *p_anchor);
|
||||
|
||||
ARKitInterface();
|
||||
~ARKitInterface();
|
||||
};
|
||||
|
||||
#endif /* !ARKIT_INTERFACE_H */
|
@ -1,791 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* arkit_interface.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "core/input/input.h"
|
||||
#include "core/os/os.h"
|
||||
#include "scene/resources/surface_tool.h"
|
||||
#include "servers/rendering/rendering_server_globals.h"
|
||||
|
||||
#import <ARKit/ARKit.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include "arkit_interface.h"
|
||||
#include "arkit_session_delegate.h"
|
||||
|
||||
// just a dirty workaround for now, declare these as globals. I'll probably encapsulate ARSession and associated logic into an mm object and change ARKitInterface to a normal cpp object that consumes it.
|
||||
API_AVAILABLE(ios(11.0))
|
||||
ARSession *ar_session;
|
||||
|
||||
ARKitSessionDelegate *ar_delegate;
|
||||
NSTimeInterval last_timestamp;
|
||||
|
||||
/* this is called when we initialize or when we come back from having our app pushed to the background, just (re)start our session */
|
||||
void ARKitInterface::start_session() {
|
||||
// We're active...
|
||||
session_was_started = true;
|
||||
|
||||
// Ignore this if we're not initialized...
|
||||
if (initialized) {
|
||||
print_line("Starting ARKit session");
|
||||
|
||||
if (@available(iOS 11, *)) {
|
||||
Class ARWorldTrackingConfigurationClass = NSClassFromString(@"ARWorldTrackingConfiguration");
|
||||
ARWorldTrackingConfiguration *configuration = [ARWorldTrackingConfigurationClass new];
|
||||
|
||||
configuration.lightEstimationEnabled = light_estimation_is_enabled;
|
||||
if (plane_detection_is_enabled) {
|
||||
if (@available(iOS 11.3, *)) {
|
||||
configuration.planeDetection = ARPlaneDetectionVertical | ARPlaneDetectionHorizontal;
|
||||
} else {
|
||||
configuration.planeDetection = ARPlaneDetectionHorizontal;
|
||||
}
|
||||
} else {
|
||||
configuration.planeDetection = 0;
|
||||
}
|
||||
|
||||
// make sure our camera is on
|
||||
if (feed.is_valid()) {
|
||||
feed->set_active(true);
|
||||
}
|
||||
|
||||
[ar_session runWithConfiguration:configuration];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ARKitInterface::stop_session() {
|
||||
session_was_started = false;
|
||||
|
||||
// Ignore this if we're not initialized...
|
||||
if (initialized) {
|
||||
// make sure our camera is off
|
||||
if (feed.is_valid()) {
|
||||
feed->set_active(false);
|
||||
}
|
||||
|
||||
if (@available(iOS 11.0, *)) {
|
||||
[ar_session pause];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ARKitInterface::notification(int p_what) {
|
||||
// TODO, this is not being called, need to find out why, possibly because this is not a node.
|
||||
// in that case we need to find a way to get these notifications!
|
||||
switch (p_what) {
|
||||
case DisplayServer::WINDOW_EVENT_FOCUS_IN: {
|
||||
print_line("Focus in");
|
||||
|
||||
start_session();
|
||||
}; break;
|
||||
case DisplayServer::WINDOW_EVENT_FOCUS_OUT: {
|
||||
print_line("Focus out");
|
||||
|
||||
stop_session();
|
||||
}; break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool ARKitInterface::get_anchor_detection_is_enabled() const {
|
||||
return plane_detection_is_enabled;
|
||||
}
|
||||
|
||||
void ARKitInterface::set_anchor_detection_is_enabled(bool p_enable) {
|
||||
if (plane_detection_is_enabled != p_enable) {
|
||||
plane_detection_is_enabled = p_enable;
|
||||
|
||||
// Restart our session (this will be ignore if we're not initialised)
|
||||
if (session_was_started) {
|
||||
start_session();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ARKitInterface::get_camera_feed_id() {
|
||||
if (feed.is_null()) {
|
||||
return 0;
|
||||
} else {
|
||||
return feed->get_id();
|
||||
}
|
||||
}
|
||||
|
||||
bool ARKitInterface::get_light_estimation_is_enabled() const {
|
||||
return light_estimation_is_enabled;
|
||||
}
|
||||
|
||||
void ARKitInterface::set_light_estimation_is_enabled(bool p_enable) {
|
||||
if (light_estimation_is_enabled != p_enable) {
|
||||
light_estimation_is_enabled = p_enable;
|
||||
|
||||
// Restart our session (this will be ignore if we're not initialised)
|
||||
if (session_was_started) {
|
||||
start_session();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
real_t ARKitInterface::get_ambient_intensity() const {
|
||||
return ambient_intensity;
|
||||
}
|
||||
|
||||
real_t ARKitInterface::get_ambient_color_temperature() const {
|
||||
return ambient_color_temperature;
|
||||
}
|
||||
|
||||
StringName ARKitInterface::get_name() const {
|
||||
return "ARKit";
|
||||
}
|
||||
|
||||
int ARKitInterface::get_capabilities() const {
|
||||
return ARKitInterface::XR_MONO + ARKitInterface::XR_AR;
|
||||
}
|
||||
|
||||
Array ARKitInterface::raycast(Vector2 p_screen_coord) {
|
||||
if (@available(iOS 11, *)) {
|
||||
Array arr;
|
||||
Size2 screen_size = DisplayServer::get_singleton()->screen_get_size();
|
||||
CGPoint point;
|
||||
point.x = p_screen_coord.x / screen_size.x;
|
||||
point.y = p_screen_coord.y / screen_size.y;
|
||||
|
||||
///@TODO maybe give more options here, for now we're taking just ARAchors into account that were found during plane detection keeping their size into account
|
||||
|
||||
NSArray<ARHitTestResult *> *results = [ar_session.currentFrame hitTest:point types:ARHitTestResultTypeExistingPlaneUsingExtent];
|
||||
|
||||
for (ARHitTestResult *result in results) {
|
||||
Transform transform;
|
||||
|
||||
matrix_float4x4 m44 = result.worldTransform;
|
||||
transform.basis.elements[0].x = m44.columns[0][0];
|
||||
transform.basis.elements[1].x = m44.columns[0][1];
|
||||
transform.basis.elements[2].x = m44.columns[0][2];
|
||||
transform.basis.elements[0].y = m44.columns[1][0];
|
||||
transform.basis.elements[1].y = m44.columns[1][1];
|
||||
transform.basis.elements[2].y = m44.columns[1][2];
|
||||
transform.basis.elements[0].z = m44.columns[2][0];
|
||||
transform.basis.elements[1].z = m44.columns[2][1];
|
||||
transform.basis.elements[2].z = m44.columns[2][2];
|
||||
transform.origin.x = m44.columns[3][0];
|
||||
transform.origin.y = m44.columns[3][1];
|
||||
transform.origin.z = m44.columns[3][2];
|
||||
|
||||
/* important, NOT scaled to world_scale !! */
|
||||
arr.push_back(transform);
|
||||
}
|
||||
|
||||
return arr;
|
||||
} else {
|
||||
return Array();
|
||||
}
|
||||
}
|
||||
|
||||
void ARKitInterface::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("_notification", "what"), &ARKitInterface::_notification);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_light_estimation_is_enabled", "enable"), &ARKitInterface::set_light_estimation_is_enabled);
|
||||
ClassDB::bind_method(D_METHOD("get_light_estimation_is_enabled"), &ARKitInterface::get_light_estimation_is_enabled);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "light_estimation"), "set_light_estimation_is_enabled", "get_light_estimation_is_enabled");
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_ambient_intensity"), &ARKitInterface::get_ambient_intensity);
|
||||
ClassDB::bind_method(D_METHOD("get_ambient_color_temperature"), &ARKitInterface::get_ambient_color_temperature);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("raycast", "screen_coord"), &ARKitInterface::raycast);
|
||||
}
|
||||
|
||||
bool ARKitInterface::is_stereo() {
|
||||
// this is a mono device...
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ARKitInterface::is_initialized() const {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
bool ARKitInterface::initialize() {
|
||||
XRServer *xr_server = XRServer::get_singleton();
|
||||
ERR_FAIL_NULL_V(xr_server, false);
|
||||
|
||||
if (@available(iOS 11, *)) {
|
||||
if (!initialized) {
|
||||
print_line("initializing ARKit");
|
||||
|
||||
// create our ar session and delegate
|
||||
Class ARSessionClass = NSClassFromString(@"ARSession");
|
||||
if (ARSessionClass == Nil) {
|
||||
void *arkit_handle = dlopen("/System/Library/Frameworks/ARKit.framework/ARKit", RTLD_NOW);
|
||||
if (arkit_handle) {
|
||||
ARSessionClass = NSClassFromString(@"ARSession");
|
||||
} else {
|
||||
print_line("ARKit init failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ar_session = [ARSessionClass new];
|
||||
ar_delegate = [ARKitSessionDelegate new];
|
||||
ar_delegate.arkit_interface = this;
|
||||
ar_session.delegate = ar_delegate;
|
||||
|
||||
// reset our transform
|
||||
transform = Transform();
|
||||
|
||||
// make this our primary interface
|
||||
xr_server->set_primary_interface(this);
|
||||
|
||||
// make sure we have our feed setup
|
||||
if (feed.is_null()) {
|
||||
feed.instance();
|
||||
feed->set_name("ARKit");
|
||||
|
||||
CameraServer *cs = CameraServer::get_singleton();
|
||||
if (cs != NULL) {
|
||||
cs->add_feed(feed);
|
||||
}
|
||||
}
|
||||
feed->set_active(true);
|
||||
|
||||
// yeah!
|
||||
initialized = true;
|
||||
|
||||
// Start our session...
|
||||
start_session();
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ARKitInterface::uninitialize() {
|
||||
if (initialized) {
|
||||
XRServer *xr_server = XRServer::get_singleton();
|
||||
if (xr_server != NULL) {
|
||||
// no longer our primary interface
|
||||
xr_server->clear_primary_interface_if(this);
|
||||
}
|
||||
|
||||
if (feed.is_valid()) {
|
||||
CameraServer *cs = CameraServer::get_singleton();
|
||||
if ((cs != NULL)) {
|
||||
cs->remove_feed(feed);
|
||||
}
|
||||
feed.unref();
|
||||
}
|
||||
|
||||
remove_all_anchors();
|
||||
|
||||
if (@available(iOS 11.0, *)) {
|
||||
ar_session = nil;
|
||||
}
|
||||
|
||||
ar_delegate = nil;
|
||||
initialized = false;
|
||||
session_was_started = false;
|
||||
}
|
||||
}
|
||||
|
||||
Size2 ARKitInterface::get_render_targetsize() {
|
||||
// _THREAD_SAFE_METHOD_
|
||||
|
||||
Size2 target_size = DisplayServer::get_singleton()->screen_get_size();
|
||||
|
||||
return target_size;
|
||||
}
|
||||
|
||||
Transform ARKitInterface::get_transform_for_eye(XRInterface::Eyes p_eye, const Transform &p_cam_transform) {
|
||||
// _THREAD_SAFE_METHOD_
|
||||
|
||||
Transform transform_for_eye;
|
||||
|
||||
XRServer *xr_server = XRServer::get_singleton();
|
||||
ERR_FAIL_NULL_V(xr_server, transform_for_eye);
|
||||
|
||||
if (initialized) {
|
||||
float world_scale = xr_server->get_world_scale();
|
||||
|
||||
// just scale our origin point of our transform, note that we really shouldn't be using world_scale in ARKit but....
|
||||
transform_for_eye = transform;
|
||||
transform_for_eye.origin *= world_scale;
|
||||
|
||||
transform_for_eye = p_cam_transform * xr_server->get_reference_frame() * transform_for_eye;
|
||||
} else {
|
||||
// huh? well just return what we got....
|
||||
transform_for_eye = p_cam_transform;
|
||||
}
|
||||
|
||||
return transform_for_eye;
|
||||
}
|
||||
|
||||
CameraMatrix ARKitInterface::get_projection_for_eye(XRInterface::Eyes p_eye, real_t p_aspect, real_t p_z_near, real_t p_z_far) {
|
||||
// Remember our near and far, it will be used in process when we obtain our projection from our ARKit session.
|
||||
z_near = p_z_near;
|
||||
z_far = p_z_far;
|
||||
|
||||
return projection;
|
||||
}
|
||||
|
||||
void ARKitInterface::commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect) {
|
||||
// _THREAD_SAFE_METHOD_
|
||||
|
||||
// We must have a valid render target
|
||||
ERR_FAIL_COND(!p_render_target.is_valid());
|
||||
|
||||
// Because we are rendering to our device we must use our main viewport!
|
||||
ERR_FAIL_COND(p_screen_rect == Rect2());
|
||||
|
||||
// get the size of our screen
|
||||
// Rect2 screen_rect = p_screen_rect;
|
||||
|
||||
// screen_rect.position.x += screen_rect.size.x;
|
||||
// screen_rect.size.x = -screen_rect.size.x;
|
||||
// screen_rect.position.y += screen_rect.size.y;
|
||||
// screen_rect.size.y = -screen_rect.size.y;
|
||||
|
||||
// VSG::rasterizer->set_current_render_target(RID());
|
||||
// VSG::rasterizer->blit_render_target_to_screen(p_render_target, screen_rect, 0);
|
||||
}
|
||||
|
||||
XRPositionalTracker *ARKitInterface::get_anchor_for_uuid(const unsigned char *p_uuid) {
|
||||
if (anchors == NULL) {
|
||||
num_anchors = 0;
|
||||
max_anchors = 10;
|
||||
anchors = (anchor_map *)malloc(sizeof(anchor_map) * max_anchors);
|
||||
}
|
||||
|
||||
ERR_FAIL_NULL_V(anchors, NULL);
|
||||
|
||||
for (unsigned int i = 0; i < num_anchors; i++) {
|
||||
if (memcmp(anchors[i].uuid, p_uuid, 16) == 0) {
|
||||
return anchors[i].tracker;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_anchors + 1 == max_anchors) {
|
||||
max_anchors += 10;
|
||||
anchors = (anchor_map *)realloc(anchors, sizeof(anchor_map) * max_anchors);
|
||||
ERR_FAIL_NULL_V(anchors, NULL);
|
||||
}
|
||||
|
||||
XRPositionalTracker *new_tracker = memnew(XRPositionalTracker);
|
||||
new_tracker->set_tracker_type(XRServer::TRACKER_ANCHOR);
|
||||
|
||||
char tracker_name[256];
|
||||
sprintf(tracker_name, "Anchor %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", p_uuid[0], p_uuid[1], p_uuid[2], p_uuid[3], p_uuid[4], p_uuid[5], p_uuid[6], p_uuid[7], p_uuid[8], p_uuid[9], p_uuid[10], p_uuid[11], p_uuid[12], p_uuid[13], p_uuid[14], p_uuid[15]);
|
||||
|
||||
String name = tracker_name;
|
||||
print_line("Adding tracker " + name);
|
||||
new_tracker->set_tracker_name(name);
|
||||
|
||||
// add our tracker
|
||||
XRServer::get_singleton()->add_tracker(new_tracker);
|
||||
anchors[num_anchors].tracker = new_tracker;
|
||||
memcpy(anchors[num_anchors].uuid, p_uuid, 16);
|
||||
num_anchors++;
|
||||
|
||||
return new_tracker;
|
||||
}
|
||||
|
||||
void ARKitInterface::remove_anchor_for_uuid(const unsigned char *p_uuid) {
|
||||
if (anchors != NULL) {
|
||||
for (unsigned int i = 0; i < num_anchors; i++) {
|
||||
if (memcmp(anchors[i].uuid, p_uuid, 16) == 0) {
|
||||
// remove our tracker
|
||||
XRServer::get_singleton()->remove_tracker(anchors[i].tracker);
|
||||
memdelete(anchors[i].tracker);
|
||||
|
||||
// bring remaining forward
|
||||
for (unsigned int j = i + 1; j < num_anchors; j++) {
|
||||
anchors[j - 1] = anchors[j];
|
||||
};
|
||||
|
||||
// decrease count
|
||||
num_anchors--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ARKitInterface::remove_all_anchors() {
|
||||
if (anchors != NULL) {
|
||||
for (unsigned int i = 0; i < num_anchors; i++) {
|
||||
// remove our tracker
|
||||
XRServer::get_singleton()->remove_tracker(anchors[i].tracker);
|
||||
memdelete(anchors[i].tracker);
|
||||
};
|
||||
|
||||
free(anchors);
|
||||
anchors = NULL;
|
||||
num_anchors = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ARKitInterface::process() {
|
||||
// _THREAD_SAFE_METHOD_
|
||||
|
||||
if (@available(iOS 11.0, *)) {
|
||||
if (initialized) {
|
||||
// get our next ARFrame
|
||||
ARFrame *current_frame = ar_session.currentFrame;
|
||||
if (last_timestamp != current_frame.timestamp) {
|
||||
// only process if we have a new frame
|
||||
last_timestamp = current_frame.timestamp;
|
||||
|
||||
// get some info about our screen and orientation
|
||||
Size2 screen_size = DisplayServer::get_singleton()->screen_get_size();
|
||||
UIInterfaceOrientation orientation = UIInterfaceOrientationUnknown;
|
||||
|
||||
if (@available(iOS 13, *)) {
|
||||
orientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
|
||||
#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
|
||||
} else {
|
||||
orientation = [[UIApplication sharedApplication] statusBarOrientation];
|
||||
#endif
|
||||
}
|
||||
|
||||
// Grab our camera image for our backbuffer
|
||||
CVPixelBufferRef pixelBuffer = current_frame.capturedImage;
|
||||
if ((CVPixelBufferGetPlaneCount(pixelBuffer) == 2) && (feed != NULL)) {
|
||||
// Plane 0 is our Y and Plane 1 is our CbCr buffer
|
||||
|
||||
// ignored, we check each plane separately
|
||||
// image_width = CVPixelBufferGetWidth(pixelBuffer);
|
||||
// image_height = CVPixelBufferGetHeight(pixelBuffer);
|
||||
|
||||
// printf("Pixel buffer %i - %i\n", image_width, image_height);
|
||||
|
||||
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
|
||||
// get our buffers
|
||||
unsigned char *dataY = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
|
||||
unsigned char *dataCbCr = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
|
||||
|
||||
if (dataY == NULL) {
|
||||
print_line("Couldn't access Y pixel buffer data");
|
||||
} else if (dataCbCr == NULL) {
|
||||
print_line("Couldn't access CbCr pixel buffer data");
|
||||
} else {
|
||||
Ref<Image> img[2];
|
||||
size_t extraLeft, extraRight, extraTop, extraBottom;
|
||||
|
||||
CVPixelBufferGetExtendedPixels(pixelBuffer, &extraLeft, &extraRight, &extraTop, &extraBottom);
|
||||
|
||||
{
|
||||
// do Y
|
||||
size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
|
||||
size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
|
||||
size_t bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
|
||||
|
||||
if ((image_width[0] != new_width) || (image_height[0] != new_height)) {
|
||||
printf("- Camera padding l:%lu r:%lu t:%lu b:%lu\n", extraLeft, extraRight, extraTop, extraBottom);
|
||||
printf("- Camera Y plane size: %lu, %lu - %lu\n", new_width, new_height, bytes_per_row);
|
||||
|
||||
image_width[0] = new_width;
|
||||
image_height[0] = new_height;
|
||||
img_data[0].resize(new_width * new_height);
|
||||
}
|
||||
|
||||
uint8_t *w = img_data[0].ptrw();
|
||||
if (new_width == bytes_per_row) {
|
||||
memcpy(w, dataY, new_width * new_height);
|
||||
} else {
|
||||
size_t offset_a = 0;
|
||||
size_t offset_b = extraLeft + (extraTop * bytes_per_row);
|
||||
for (size_t r = 0; r < new_height; r++) {
|
||||
memcpy(w + offset_a, dataY + offset_b, new_width);
|
||||
offset_a += new_width;
|
||||
offset_b += bytes_per_row;
|
||||
}
|
||||
}
|
||||
|
||||
img[0].instance();
|
||||
img[0]->create(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]);
|
||||
}
|
||||
|
||||
{
|
||||
// do CbCr
|
||||
size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
|
||||
size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
|
||||
size_t bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
|
||||
|
||||
if ((image_width[1] != new_width) || (image_height[1] != new_height)) {
|
||||
printf("- Camera CbCr plane size: %lu, %lu - %lu\n", new_width, new_height, bytes_per_row);
|
||||
|
||||
image_width[1] = new_width;
|
||||
image_height[1] = new_height;
|
||||
img_data[1].resize(2 * new_width * new_height);
|
||||
}
|
||||
|
||||
uint8_t *w = img_data[1].ptrw();
|
||||
if ((2 * new_width) == bytes_per_row) {
|
||||
memcpy(w, dataCbCr, 2 * new_width * new_height);
|
||||
} else {
|
||||
size_t offset_a = 0;
|
||||
size_t offset_b = extraLeft + (extraTop * bytes_per_row);
|
||||
for (size_t r = 0; r < new_height; r++) {
|
||||
memcpy(w + offset_a, dataCbCr + offset_b, 2 * new_width);
|
||||
offset_a += 2 * new_width;
|
||||
offset_b += bytes_per_row;
|
||||
}
|
||||
}
|
||||
|
||||
img[1].instance();
|
||||
img[1]->create(new_width, new_height, 0, Image::FORMAT_RG8, img_data[1]);
|
||||
}
|
||||
|
||||
// set our texture...
|
||||
feed->set_YCbCr_imgs(img[0], img[1]);
|
||||
|
||||
// now build our transform to display this as a background image that matches our camera
|
||||
CGAffineTransform affine_transform = [current_frame displayTransformForOrientation:orientation viewportSize:CGSizeMake(screen_size.width, screen_size.height)];
|
||||
|
||||
// we need to invert this, probably row v.s. column notation
|
||||
affine_transform = CGAffineTransformInvert(affine_transform);
|
||||
|
||||
if (orientation != UIInterfaceOrientationPortrait) {
|
||||
affine_transform.b = -affine_transform.b;
|
||||
affine_transform.d = -affine_transform.d;
|
||||
affine_transform.ty = 1.0 - affine_transform.ty;
|
||||
} else {
|
||||
affine_transform.c = -affine_transform.c;
|
||||
affine_transform.a = -affine_transform.a;
|
||||
affine_transform.tx = 1.0 - affine_transform.tx;
|
||||
}
|
||||
|
||||
Transform2D display_transform = Transform2D(
|
||||
affine_transform.a, affine_transform.b,
|
||||
affine_transform.c, affine_transform.d,
|
||||
affine_transform.tx, affine_transform.ty);
|
||||
|
||||
feed->set_transform(display_transform);
|
||||
}
|
||||
|
||||
// and unlock
|
||||
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
}
|
||||
|
||||
// Record light estimation to apply to our scene
|
||||
if (light_estimation_is_enabled) {
|
||||
ambient_intensity = current_frame.lightEstimate.ambientIntensity;
|
||||
|
||||
///@TODO it's there, but not there.. what to do with this...
|
||||
// https://developer.apple.com/documentation/arkit/arlightestimate?language=objc
|
||||
// ambient_color_temperature = current_frame.lightEstimate.ambientColorTemperature;
|
||||
}
|
||||
|
||||
// Process our camera
|
||||
ARCamera *camera = current_frame.camera;
|
||||
|
||||
// strangely enough we have to states, rolling them up into one
|
||||
if (camera.trackingState == ARTrackingStateNotAvailable) {
|
||||
// no tracking, would be good if we black out the screen or something...
|
||||
tracking_state = XRInterface::XR_NOT_TRACKING;
|
||||
} else {
|
||||
if (camera.trackingState == ARTrackingStateNormal) {
|
||||
tracking_state = XRInterface::XR_NORMAL_TRACKING;
|
||||
} else if (camera.trackingStateReason == ARTrackingStateReasonExcessiveMotion) {
|
||||
tracking_state = XRInterface::XR_EXCESSIVE_MOTION;
|
||||
} else if (camera.trackingStateReason == ARTrackingStateReasonInsufficientFeatures) {
|
||||
tracking_state = XRInterface::XR_INSUFFICIENT_FEATURES;
|
||||
} else {
|
||||
tracking_state = XRInterface::XR_UNKNOWN_TRACKING;
|
||||
}
|
||||
|
||||
// copy our current frame transform
|
||||
matrix_float4x4 m44 = camera.transform;
|
||||
if (orientation == UIInterfaceOrientationLandscapeLeft) {
|
||||
transform.basis.elements[0].x = m44.columns[0][0];
|
||||
transform.basis.elements[1].x = m44.columns[0][1];
|
||||
transform.basis.elements[2].x = m44.columns[0][2];
|
||||
transform.basis.elements[0].y = m44.columns[1][0];
|
||||
transform.basis.elements[1].y = m44.columns[1][1];
|
||||
transform.basis.elements[2].y = m44.columns[1][2];
|
||||
} else if (orientation == UIInterfaceOrientationPortrait) {
|
||||
transform.basis.elements[0].x = m44.columns[1][0];
|
||||
transform.basis.elements[1].x = m44.columns[1][1];
|
||||
transform.basis.elements[2].x = m44.columns[1][2];
|
||||
transform.basis.elements[0].y = -m44.columns[0][0];
|
||||
transform.basis.elements[1].y = -m44.columns[0][1];
|
||||
transform.basis.elements[2].y = -m44.columns[0][2];
|
||||
} else if (orientation == UIInterfaceOrientationLandscapeRight) {
|
||||
transform.basis.elements[0].x = -m44.columns[0][0];
|
||||
transform.basis.elements[1].x = -m44.columns[0][1];
|
||||
transform.basis.elements[2].x = -m44.columns[0][2];
|
||||
transform.basis.elements[0].y = -m44.columns[1][0];
|
||||
transform.basis.elements[1].y = -m44.columns[1][1];
|
||||
transform.basis.elements[2].y = -m44.columns[1][2];
|
||||
} else if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
|
||||
// this may not be correct
|
||||
transform.basis.elements[0].x = m44.columns[1][0];
|
||||
transform.basis.elements[1].x = m44.columns[1][1];
|
||||
transform.basis.elements[2].x = m44.columns[1][2];
|
||||
transform.basis.elements[0].y = m44.columns[0][0];
|
||||
transform.basis.elements[1].y = m44.columns[0][1];
|
||||
transform.basis.elements[2].y = m44.columns[0][2];
|
||||
}
|
||||
transform.basis.elements[0].z = m44.columns[2][0];
|
||||
transform.basis.elements[1].z = m44.columns[2][1];
|
||||
transform.basis.elements[2].z = m44.columns[2][2];
|
||||
transform.origin.x = m44.columns[3][0];
|
||||
transform.origin.y = m44.columns[3][1];
|
||||
transform.origin.z = m44.columns[3][2];
|
||||
|
||||
// copy our current frame projection, investigate using projectionMatrixWithViewportSize:orientation:zNear:zFar: so we can set our own near and far
|
||||
m44 = [camera projectionMatrixForOrientation:orientation viewportSize:CGSizeMake(screen_size.width, screen_size.height) zNear:z_near zFar:z_far];
|
||||
projection.matrix[0][0] = m44.columns[0][0];
|
||||
projection.matrix[1][0] = m44.columns[1][0];
|
||||
projection.matrix[2][0] = m44.columns[2][0];
|
||||
projection.matrix[3][0] = m44.columns[3][0];
|
||||
projection.matrix[0][1] = m44.columns[0][1];
|
||||
projection.matrix[1][1] = m44.columns[1][1];
|
||||
projection.matrix[2][1] = m44.columns[2][1];
|
||||
projection.matrix[3][1] = m44.columns[3][1];
|
||||
projection.matrix[0][2] = m44.columns[0][2];
|
||||
projection.matrix[1][2] = m44.columns[1][2];
|
||||
projection.matrix[2][2] = m44.columns[2][2];
|
||||
projection.matrix[3][2] = m44.columns[3][2];
|
||||
projection.matrix[0][3] = m44.columns[0][3];
|
||||
projection.matrix[1][3] = m44.columns[1][3];
|
||||
projection.matrix[2][3] = m44.columns[2][3];
|
||||
projection.matrix[3][3] = m44.columns[3][3];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ARKitInterface::_add_or_update_anchor(GodotARAnchor *p_anchor) {
|
||||
// _THREAD_SAFE_METHOD_
|
||||
|
||||
if (@available(iOS 11.0, *)) {
|
||||
ARAnchor *anchor = (ARAnchor *)p_anchor;
|
||||
|
||||
unsigned char uuid[16];
|
||||
[anchor.identifier getUUIDBytes:uuid];
|
||||
|
||||
XRPositionalTracker *tracker = get_anchor_for_uuid(uuid);
|
||||
if (tracker != NULL) {
|
||||
// lets update our mesh! (using Arjens code as is for now)
|
||||
// we should also probably limit how often we do this...
|
||||
|
||||
// can we safely cast this?
|
||||
ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor;
|
||||
|
||||
if (@available(iOS 11.3, *)) {
|
||||
if (planeAnchor.geometry.triangleCount > 0) {
|
||||
Ref<SurfaceTool> surftool;
|
||||
surftool.instance();
|
||||
surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
|
||||
|
||||
for (int j = planeAnchor.geometry.triangleCount * 3 - 1; j >= 0; j--) {
|
||||
int16_t index = planeAnchor.geometry.triangleIndices[j];
|
||||
simd_float3 vrtx = planeAnchor.geometry.vertices[index];
|
||||
simd_float2 textcoord = planeAnchor.geometry.textureCoordinates[index];
|
||||
surftool->set_uv(Vector2(textcoord[0], textcoord[1]));
|
||||
surftool->set_color(Color(0.8, 0.8, 0.8));
|
||||
surftool->add_vertex(Vector3(vrtx[0], vrtx[1], vrtx[2]));
|
||||
}
|
||||
|
||||
surftool->generate_normals();
|
||||
tracker->set_mesh(surftool->commit());
|
||||
} else {
|
||||
Ref<Mesh> nomesh;
|
||||
tracker->set_mesh(nomesh);
|
||||
}
|
||||
} else {
|
||||
Ref<Mesh> nomesh;
|
||||
tracker->set_mesh(nomesh);
|
||||
}
|
||||
|
||||
// Note, this also contains a scale factor which gives us an idea of the size of the anchor
|
||||
// We may extract that in our XRAnchor class
|
||||
Basis b;
|
||||
matrix_float4x4 m44 = anchor.transform;
|
||||
b.elements[0].x = m44.columns[0][0];
|
||||
b.elements[1].x = m44.columns[0][1];
|
||||
b.elements[2].x = m44.columns[0][2];
|
||||
b.elements[0].y = m44.columns[1][0];
|
||||
b.elements[1].y = m44.columns[1][1];
|
||||
b.elements[2].y = m44.columns[1][2];
|
||||
b.elements[0].z = m44.columns[2][0];
|
||||
b.elements[1].z = m44.columns[2][1];
|
||||
b.elements[2].z = m44.columns[2][2];
|
||||
tracker->set_orientation(b);
|
||||
tracker->set_rw_position(Vector3(m44.columns[3][0], m44.columns[3][1], m44.columns[3][2]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ARKitInterface::_remove_anchor(GodotARAnchor *p_anchor) {
|
||||
// _THREAD_SAFE_METHOD_
|
||||
|
||||
if (@available(iOS 11.0, *)) {
|
||||
ARAnchor *anchor = (ARAnchor *)p_anchor;
|
||||
|
||||
unsigned char uuid[16];
|
||||
[anchor.identifier getUUIDBytes:uuid];
|
||||
|
||||
remove_anchor_for_uuid(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
ARKitInterface::ARKitInterface() {
|
||||
initialized = false;
|
||||
session_was_started = false;
|
||||
plane_detection_is_enabled = false;
|
||||
light_estimation_is_enabled = false;
|
||||
if (@available(iOS 11.0, *)) {
|
||||
ar_session = nil;
|
||||
}
|
||||
z_near = 0.01;
|
||||
z_far = 1000.0;
|
||||
projection.set_perspective(60.0, 1.0, z_near, z_far, false);
|
||||
anchors = NULL;
|
||||
num_anchors = 0;
|
||||
ambient_intensity = 1.0;
|
||||
ambient_color_temperature = 1.0;
|
||||
image_width[0] = 0;
|
||||
image_width[1] = 0;
|
||||
image_height[0] = 0;
|
||||
image_height[1] = 0;
|
||||
}
|
||||
|
||||
ARKitInterface::~ARKitInterface() {
|
||||
remove_all_anchors();
|
||||
|
||||
// and make sure we cleanup if we haven't already
|
||||
if (is_initialized()) {
|
||||
uninitialize();
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* arkit_module.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "arkit_module.h"
|
||||
|
||||
#include "arkit_interface.h"
|
||||
|
||||
void register_arkit_types() {
|
||||
// does it make sense to register the class?
|
||||
|
||||
Ref<ARKitInterface> arkit_interface;
|
||||
arkit_interface.instance();
|
||||
XRServer::get_singleton()->add_interface(arkit_interface);
|
||||
}
|
||||
|
||||
void unregister_arkit_types() {
|
||||
// should clean itself up nicely :)
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* arkit_module.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef ARKIT_REGISTER_TYPES_H
|
||||
#define ARKIT_REGISTER_TYPES_H
|
||||
|
||||
void register_arkit_types();
|
||||
void unregister_arkit_types();
|
||||
|
||||
#endif // ARKIT_REGISTER_TYPES_H
|
@ -1,50 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* arkit_session_delegate.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef ARKIT_SESSION_DELEGATE_H
|
||||
#define ARKIT_SESSION_DELEGATE_H
|
||||
|
||||
#import <ARKit/ARKit.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
class ARKitInterface;
|
||||
|
||||
@interface ARKitSessionDelegate : NSObject <ARSessionDelegate> {
|
||||
ARKitInterface *arkit_interface;
|
||||
}
|
||||
|
||||
@property(nonatomic) ARKitInterface *arkit_interface;
|
||||
|
||||
- (void)session:(ARSession *)session didAddAnchors:(NSArray<ARAnchor *> *)anchors API_AVAILABLE(ios(11.0));
|
||||
- (void)session:(ARSession *)session didRemoveAnchors:(NSArray<ARAnchor *> *)anchors API_AVAILABLE(ios(11.0));
|
||||
- (void)session:(ARSession *)session didUpdateAnchors:(NSArray<ARAnchor *> *)anchors API_AVAILABLE(ios(11.0));
|
||||
@end
|
||||
|
||||
#endif /* !ARKIT_SESSION_DELEGATE_H */
|
@ -1,56 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* arkit_session_delegate.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "arkit_session_delegate.h"
|
||||
#include "arkit_interface.h"
|
||||
|
||||
@implementation ARKitSessionDelegate
|
||||
|
||||
@synthesize arkit_interface;
|
||||
|
||||
- (void)session:(ARSession *)session didAddAnchors:(NSArray<ARAnchor *> *)anchors {
|
||||
for (ARAnchor *anchor in anchors) {
|
||||
arkit_interface->_add_or_update_anchor(anchor);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)session:(ARSession *)session didRemoveAnchors:(NSArray<ARAnchor *> *)anchors {
|
||||
for (ARAnchor *anchor in anchors) {
|
||||
arkit_interface->_remove_anchor(anchor);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)session:(ARSession *)session didUpdateAnchors:(NSArray<ARAnchor *> *)anchors {
|
||||
for (ARAnchor *anchor in anchors) {
|
||||
arkit_interface->_add_or_update_anchor(anchor);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
@ -1,6 +0,0 @@
|
||||
def can_build(env, platform):
|
||||
return platform == "iphone"
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_camera = env_modules.Clone()
|
||||
|
||||
# (iOS) Enable module support
|
||||
env_camera.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
|
||||
|
||||
# (iOS) Build as separate static library
|
||||
modules_sources = []
|
||||
env_camera.add_source_files(modules_sources, "*.cpp")
|
||||
env_camera.add_source_files(modules_sources, "*.mm")
|
||||
mod_lib = env_modules.add_library("#bin/libgodot_camera_module" + env["LIBSUFFIX"], modules_sources)
|
@ -1,18 +0,0 @@
|
||||
[config]
|
||||
name="Camera"
|
||||
binary="camera_lib.a"
|
||||
|
||||
initialization="register_camera_types"
|
||||
deinitialization="unregister_camera_types"
|
||||
|
||||
[dependencies]
|
||||
linked=[]
|
||||
embedded=[]
|
||||
system=["AVFoundation.framework"]
|
||||
|
||||
capabilities=[]
|
||||
|
||||
files=[]
|
||||
|
||||
[plist]
|
||||
NSCameraUsageDescription="Device camera is used for some functionality"
|
@ -1,45 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* camera_ios.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef CAMERAIOS_H
|
||||
#define CAMERAIOS_H
|
||||
|
||||
#include "servers/camera_server.h"
|
||||
|
||||
class CameraIOS : public CameraServer {
|
||||
private:
|
||||
public:
|
||||
CameraIOS();
|
||||
~CameraIOS();
|
||||
|
||||
void update_feeds();
|
||||
};
|
||||
|
||||
#endif /* CAMERAIOS_H */
|
@ -1,445 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* camera_ios.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
///@TODO this is a near duplicate of CameraOSX, we should find a way to combine those to minimize code duplication!!!!
|
||||
// If you fix something here, make sure you fix it there as wel!
|
||||
|
||||
#include "camera_ios.h"
|
||||
#include "servers/camera/camera_feed.h"
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// MyCaptureSession - This is a little helper class so we can capture our frames
|
||||
|
||||
@interface MyCaptureSession : AVCaptureSession <AVCaptureVideoDataOutputSampleBufferDelegate> {
|
||||
Ref<CameraFeed> feed;
|
||||
size_t width[2];
|
||||
size_t height[2];
|
||||
Vector<uint8_t> img_data[2];
|
||||
|
||||
AVCaptureDeviceInput *input;
|
||||
AVCaptureVideoDataOutput *output;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MyCaptureSession
|
||||
|
||||
- (id)initForFeed:(Ref<CameraFeed>)p_feed andDevice:(AVCaptureDevice *)p_device {
|
||||
if (self = [super init]) {
|
||||
NSError *error;
|
||||
feed = p_feed;
|
||||
width[0] = 0;
|
||||
height[0] = 0;
|
||||
width[1] = 0;
|
||||
height[1] = 0;
|
||||
|
||||
// prepare our device
|
||||
[p_device lockForConfiguration:&error];
|
||||
|
||||
[p_device setFocusMode:AVCaptureFocusModeLocked];
|
||||
[p_device setExposureMode:AVCaptureExposureModeLocked];
|
||||
[p_device setWhiteBalanceMode:AVCaptureWhiteBalanceModeLocked];
|
||||
|
||||
[p_device unlockForConfiguration];
|
||||
|
||||
[self beginConfiguration];
|
||||
|
||||
// setup our capture
|
||||
self.sessionPreset = AVCaptureSessionPreset1280x720;
|
||||
|
||||
input = [AVCaptureDeviceInput deviceInputWithDevice:p_device error:&error];
|
||||
if (!input) {
|
||||
print_line("Couldn't get input device for camera");
|
||||
} else {
|
||||
[self addInput:input];
|
||||
}
|
||||
|
||||
output = [AVCaptureVideoDataOutput new];
|
||||
if (!output) {
|
||||
print_line("Couldn't get output device for camera");
|
||||
} else {
|
||||
NSDictionary *settings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) };
|
||||
output.videoSettings = settings;
|
||||
|
||||
// discard if the data output queue is blocked (as we process the still image)
|
||||
[output setAlwaysDiscardsLateVideoFrames:YES];
|
||||
|
||||
// now set ourselves as the delegate to receive new frames. Note that we're doing this on the main thread at the moment, we may need to change this..
|
||||
[output setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
|
||||
|
||||
[self addOutput:output];
|
||||
}
|
||||
|
||||
[self commitConfiguration];
|
||||
|
||||
// kick off our session..
|
||||
[self startRunning];
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)cleanup {
|
||||
// stop running
|
||||
[self stopRunning];
|
||||
|
||||
// cleanup
|
||||
[self beginConfiguration];
|
||||
|
||||
if (input) {
|
||||
[self removeInput:input];
|
||||
// don't release this
|
||||
input = nil;
|
||||
}
|
||||
|
||||
if (output) {
|
||||
[self removeOutput:output];
|
||||
[output setSampleBufferDelegate:nil queue:NULL];
|
||||
output = nil;
|
||||
}
|
||||
|
||||
[self commitConfiguration];
|
||||
}
|
||||
|
||||
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
|
||||
// This gets called every time our camera has a new image for us to process.
|
||||
// May need to investigate in a way to throttle this if we get more images then we're rendering frames..
|
||||
|
||||
// For now, version 1, we're just doing the bare minimum to make this work...
|
||||
|
||||
CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
|
||||
// int width = CVPixelBufferGetWidth(pixelBuffer);
|
||||
// int height = CVPixelBufferGetHeight(pixelBuffer);
|
||||
|
||||
// It says that we need to lock this on the documentation pages but it's not in the samples
|
||||
// need to lock our base address so we can access our pixel buffers, better safe then sorry?
|
||||
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
|
||||
// get our buffers
|
||||
unsigned char *dataY = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
|
||||
unsigned char *dataCbCr = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
|
||||
if (dataY == NULL) {
|
||||
print_line("Couldn't access Y pixel buffer data");
|
||||
} else if (dataCbCr == NULL) {
|
||||
print_line("Couldn't access CbCr pixel buffer data");
|
||||
} else {
|
||||
UIInterfaceOrientation orientation = UIInterfaceOrientationUnknown;
|
||||
|
||||
if (@available(iOS 13, *)) {
|
||||
orientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
|
||||
#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
|
||||
} else {
|
||||
orientation = [[UIApplication sharedApplication] statusBarOrientation];
|
||||
#endif
|
||||
}
|
||||
|
||||
Ref<Image> img[2];
|
||||
|
||||
{
|
||||
// do Y
|
||||
size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
|
||||
size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
|
||||
|
||||
if ((width[0] != new_width) || (height[0] != new_height)) {
|
||||
width[0] = new_width;
|
||||
height[0] = new_height;
|
||||
img_data[0].resize(new_width * new_height);
|
||||
}
|
||||
|
||||
uint8_t *w = img_data[0].ptrw();
|
||||
memcpy(w, dataY, new_width * new_height);
|
||||
|
||||
img[0].instance();
|
||||
img[0]->create(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]);
|
||||
}
|
||||
|
||||
{
|
||||
// do CbCr
|
||||
size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
|
||||
size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
|
||||
|
||||
if ((width[1] != new_width) || (height[1] != new_height)) {
|
||||
width[1] = new_width;
|
||||
height[1] = new_height;
|
||||
img_data[1].resize(2 * new_width * new_height);
|
||||
}
|
||||
|
||||
uint8_t *w = img_data[1].ptrw();
|
||||
memcpy(w, dataCbCr, 2 * new_width * new_height);
|
||||
|
||||
///TODO GLES2 doesn't support FORMAT_RG8, need to do some form of conversion
|
||||
img[1].instance();
|
||||
img[1]->create(new_width, new_height, 0, Image::FORMAT_RG8, img_data[1]);
|
||||
}
|
||||
|
||||
// set our texture...
|
||||
feed->set_YCbCr_imgs(img[0], img[1]);
|
||||
|
||||
// update our matrix to match the orientation, note, before changing anything
|
||||
// here, be aware that the project orientation settings must match your xcode
|
||||
// settings or this will go wrong!
|
||||
Transform2D display_transform;
|
||||
switch (orientation) {
|
||||
case UIInterfaceOrientationPortrait: {
|
||||
display_transform = Transform2D(0.0, -1.0, -1.0, 0.0, 1.0, 1.0);
|
||||
} break;
|
||||
case UIInterfaceOrientationLandscapeRight: {
|
||||
display_transform = Transform2D(1.0, 0.0, 0.0, -1.0, 0.0, 1.0);
|
||||
} break;
|
||||
case UIInterfaceOrientationLandscapeLeft: {
|
||||
display_transform = Transform2D(-1.0, 0.0, 0.0, 1.0, 1.0, 0.0);
|
||||
} break;
|
||||
default: {
|
||||
display_transform = Transform2D(0.0, 1.0, 1.0, 0.0, 0.0, 0.0);
|
||||
} break;
|
||||
}
|
||||
|
||||
//TODO: this is correct for the camera on the back, I have a feeling this needs to be inversed for the camera on the front!
|
||||
feed->set_transform(display_transform);
|
||||
}
|
||||
|
||||
// and unlock
|
||||
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// CameraFeedIOS - Subclass for camera feeds in iOS
|
||||
|
||||
class CameraFeedIOS : public CameraFeed {
|
||||
private:
|
||||
AVCaptureDevice *device;
|
||||
MyCaptureSession *capture_session;
|
||||
|
||||
public:
|
||||
bool get_is_arkit() const;
|
||||
AVCaptureDevice *get_device() const;
|
||||
|
||||
CameraFeedIOS();
|
||||
~CameraFeedIOS();
|
||||
|
||||
void set_device(AVCaptureDevice *p_device);
|
||||
|
||||
bool activate_feed();
|
||||
void deactivate_feed();
|
||||
};
|
||||
|
||||
AVCaptureDevice *CameraFeedIOS::get_device() const {
|
||||
return device;
|
||||
};
|
||||
|
||||
CameraFeedIOS::CameraFeedIOS() {
|
||||
capture_session = NULL;
|
||||
device = NULL;
|
||||
transform = Transform2D(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); /* should re-orientate this based on device orientation */
|
||||
};
|
||||
|
||||
void CameraFeedIOS::set_device(AVCaptureDevice *p_device) {
|
||||
device = p_device;
|
||||
|
||||
// get some info
|
||||
NSString *device_name = p_device.localizedName;
|
||||
name = device_name.UTF8String;
|
||||
position = CameraFeed::FEED_UNSPECIFIED;
|
||||
if ([p_device position] == AVCaptureDevicePositionBack) {
|
||||
position = CameraFeed::FEED_BACK;
|
||||
} else if ([p_device position] == AVCaptureDevicePositionFront) {
|
||||
position = CameraFeed::FEED_FRONT;
|
||||
};
|
||||
};
|
||||
|
||||
CameraFeedIOS::~CameraFeedIOS() {
|
||||
if (capture_session) {
|
||||
capture_session = nil;
|
||||
};
|
||||
|
||||
if (device) {
|
||||
device = nil;
|
||||
};
|
||||
};
|
||||
|
||||
bool CameraFeedIOS::activate_feed() {
|
||||
if (capture_session) {
|
||||
// already recording!
|
||||
} else {
|
||||
// start camera capture
|
||||
capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device];
|
||||
};
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
void CameraFeedIOS::deactivate_feed() {
|
||||
// end camera capture if we have one
|
||||
if (capture_session) {
|
||||
[capture_session cleanup];
|
||||
capture_session = nil;
|
||||
};
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// MyDeviceNotifications - This is a little helper class gets notifications
|
||||
// when devices are connected/disconnected
|
||||
|
||||
@interface MyDeviceNotifications : NSObject {
|
||||
CameraIOS *camera_server;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MyDeviceNotifications
|
||||
|
||||
- (void)devices_changed:(NSNotification *)notification {
|
||||
camera_server->update_feeds();
|
||||
}
|
||||
|
||||
- (id)initForServer:(CameraIOS *)p_server {
|
||||
if (self = [super init]) {
|
||||
camera_server = p_server;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasConnectedNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasDisconnectedNotification object:nil];
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
// remove notifications
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
MyDeviceNotifications *device_notifications = nil;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// CameraIOS - Subclass for our camera server on iPhone
|
||||
|
||||
void CameraIOS::update_feeds() {
|
||||
// this way of doing things is deprecated but still works,
|
||||
// rewrite to using AVCaptureDeviceDiscoverySession
|
||||
|
||||
NSMutableArray *deviceTypes = [NSMutableArray array];
|
||||
|
||||
if (@available(iOS 10, *)) {
|
||||
[deviceTypes addObject:AVCaptureDeviceTypeBuiltInWideAngleCamera];
|
||||
[deviceTypes addObject:AVCaptureDeviceTypeBuiltInTelephotoCamera];
|
||||
|
||||
if (@available(iOS 10.2, *)) {
|
||||
[deviceTypes addObject:AVCaptureDeviceTypeBuiltInDualCamera];
|
||||
}
|
||||
|
||||
if (@available(iOS 11.1, *)) {
|
||||
[deviceTypes addObject:AVCaptureDeviceTypeBuiltInTrueDepthCamera];
|
||||
}
|
||||
|
||||
AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession
|
||||
discoverySessionWithDeviceTypes:deviceTypes
|
||||
mediaType:AVMediaTypeVideo
|
||||
position:AVCaptureDevicePositionUnspecified];
|
||||
|
||||
// remove devices that are gone..
|
||||
for (int i = feeds.size() - 1; i >= 0; i--) {
|
||||
Ref<CameraFeedIOS> feed(feeds[i]);
|
||||
|
||||
if (feed.is_null()) {
|
||||
// feed not managed by us
|
||||
} else if (![session.devices containsObject:feed->get_device()]) {
|
||||
// remove it from our array, this will also destroy it ;)
|
||||
remove_feed(feed);
|
||||
};
|
||||
};
|
||||
|
||||
// add new devices..
|
||||
for (AVCaptureDevice *device in session.devices) {
|
||||
bool found = false;
|
||||
|
||||
for (int i = 0; i < feeds.size() && !found; i++) {
|
||||
Ref<CameraFeedIOS> feed(feeds[i]);
|
||||
|
||||
if (feed.is_null()) {
|
||||
// feed not managed by us
|
||||
} else if (feed->get_device() == device) {
|
||||
found = true;
|
||||
};
|
||||
};
|
||||
|
||||
if (!found) {
|
||||
Ref<CameraFeedIOS> newfeed;
|
||||
newfeed.instance();
|
||||
newfeed->set_device(device);
|
||||
add_feed(newfeed);
|
||||
};
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
CameraIOS::CameraIOS() {
|
||||
// check if we have our usage description
|
||||
NSString *usage_desc = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSCameraUsageDescription"];
|
||||
if (usage_desc == NULL) {
|
||||
// don't initialise if we don't get anything
|
||||
print_line("No NSCameraUsageDescription key in pList, no access to cameras.");
|
||||
return;
|
||||
} else if (usage_desc.length == 0) {
|
||||
// don't initialise if we don't get anything
|
||||
print_line("Empty NSCameraUsageDescription key in pList, no access to cameras.");
|
||||
return;
|
||||
}
|
||||
|
||||
// now we'll request access.
|
||||
// If this is the first time the user will be prompted with the string (iOS will read it).
|
||||
// Once a decision is made it is returned. If the user wants to change it later on they
|
||||
// need to go into setting.
|
||||
print_line("Requesting Camera permissions");
|
||||
|
||||
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
|
||||
completionHandler:^(BOOL granted) {
|
||||
if (granted) {
|
||||
print_line("Access to cameras granted!");
|
||||
|
||||
// Find available cameras we have at this time
|
||||
update_feeds();
|
||||
|
||||
// should only have one of these....
|
||||
device_notifications = [[MyDeviceNotifications alloc] initForServer:this];
|
||||
} else {
|
||||
print_line("No access to cameras!");
|
||||
}
|
||||
}];
|
||||
};
|
||||
|
||||
CameraIOS::~CameraIOS() {
|
||||
device_notifications = nil;
|
||||
};
|
@ -1,40 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* camera_module.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "camera_module.h"
|
||||
|
||||
#include "camera_ios.h"
|
||||
|
||||
void register_camera_types() {
|
||||
CameraServer::make_default<CameraIOS>();
|
||||
}
|
||||
|
||||
void unregister_camera_types() {
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* camera_module.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
void register_camera_types();
|
||||
void unregister_camera_types();
|
@ -1,6 +0,0 @@
|
||||
def can_build(env, platform):
|
||||
return platform == "iphone"
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_gamecenter = env_modules.Clone()
|
||||
|
||||
# (iOS) Enable module support
|
||||
env_gamecenter.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
|
||||
|
||||
# (iOS) Build as separate static library
|
||||
modules_sources = []
|
||||
env_gamecenter.add_source_files(modules_sources, "*.cpp")
|
||||
env_gamecenter.add_source_files(modules_sources, "*.mm")
|
||||
mod_lib = env_modules.add_library("#bin/libgodot_gamecenter_module" + env["LIBSUFFIX"], modules_sources)
|
@ -1,6 +0,0 @@
|
||||
def can_build(env, platform):
|
||||
return platform == "iphone"
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
@ -1,71 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* game_center.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef GAME_CENTER_H
|
||||
#define GAME_CENTER_H
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
|
||||
class GameCenter : public Object {
|
||||
GDCLASS(GameCenter, Object);
|
||||
|
||||
static GameCenter *instance;
|
||||
static void _bind_methods();
|
||||
|
||||
List<Variant> pending_events;
|
||||
|
||||
bool authenticated;
|
||||
|
||||
void return_connect_error(const char *p_error_description);
|
||||
|
||||
public:
|
||||
Error authenticate();
|
||||
bool is_authenticated();
|
||||
|
||||
Error post_score(Dictionary p_score);
|
||||
Error award_achievement(Dictionary p_params);
|
||||
void reset_achievements();
|
||||
void request_achievements();
|
||||
void request_achievement_descriptions();
|
||||
Error show_game_center(Dictionary p_params);
|
||||
Error request_identity_verification_signature();
|
||||
|
||||
void game_center_closed();
|
||||
|
||||
int get_pending_event_count();
|
||||
Variant pop_pending_event();
|
||||
|
||||
static GameCenter *get_singleton();
|
||||
|
||||
GameCenter();
|
||||
~GameCenter();
|
||||
};
|
||||
|
||||
#endif
|
@ -1,380 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* game_center.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "game_center.h"
|
||||
#import "platform/iphone/app_delegate.h"
|
||||
|
||||
#import "game_center_delegate.h"
|
||||
#import "platform/iphone/view_controller.h"
|
||||
#import <GameKit/GameKit.h>
|
||||
|
||||
GameCenter *GameCenter::instance = NULL;
|
||||
GodotGameCenterDelegate *gameCenterDelegate = nil;
|
||||
|
||||
void GameCenter::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("authenticate"), &GameCenter::authenticate);
|
||||
ClassDB::bind_method(D_METHOD("is_authenticated"), &GameCenter::is_authenticated);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("post_score"), &GameCenter::post_score);
|
||||
ClassDB::bind_method(D_METHOD("award_achievement", "achievement"), &GameCenter::award_achievement);
|
||||
ClassDB::bind_method(D_METHOD("reset_achievements"), &GameCenter::reset_achievements);
|
||||
ClassDB::bind_method(D_METHOD("request_achievements"), &GameCenter::request_achievements);
|
||||
ClassDB::bind_method(D_METHOD("request_achievement_descriptions"), &GameCenter::request_achievement_descriptions);
|
||||
ClassDB::bind_method(D_METHOD("show_game_center"), &GameCenter::show_game_center);
|
||||
ClassDB::bind_method(D_METHOD("request_identity_verification_signature"), &GameCenter::request_identity_verification_signature);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_pending_event_count"), &GameCenter::get_pending_event_count);
|
||||
ClassDB::bind_method(D_METHOD("pop_pending_event"), &GameCenter::pop_pending_event);
|
||||
};
|
||||
|
||||
Error GameCenter::authenticate() {
|
||||
//if this class isn't available, game center isn't implemented
|
||||
if ((NSClassFromString(@"GKLocalPlayer")) == nil) {
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
GKLocalPlayer *player = [GKLocalPlayer localPlayer];
|
||||
ERR_FAIL_COND_V(![player respondsToSelector:@selector(authenticateHandler)], ERR_UNAVAILABLE);
|
||||
|
||||
UIViewController *root_controller = [[UIApplication sharedApplication] delegate].window.rootViewController;
|
||||
ERR_FAIL_COND_V(!root_controller, FAILED);
|
||||
|
||||
// This handler is called several times. First when the view needs to be shown, then again
|
||||
// after the view is cancelled or the user logs in. Or if the user's already logged in, it's
|
||||
// called just once to confirm they're authenticated. This is why no result needs to be specified
|
||||
// in the presentViewController phase. In this case, more calls to this function will follow.
|
||||
_weakify(root_controller);
|
||||
_weakify(player);
|
||||
player.authenticateHandler = (^(UIViewController *controller, NSError *error) {
|
||||
_strongify(root_controller);
|
||||
_strongify(player);
|
||||
|
||||
if (controller) {
|
||||
[root_controller presentViewController:controller animated:YES completion:nil];
|
||||
} else {
|
||||
Dictionary ret;
|
||||
ret["type"] = "authentication";
|
||||
if (player.isAuthenticated) {
|
||||
ret["result"] = "ok";
|
||||
if (@available(iOS 13, *)) {
|
||||
ret["player_id"] = [player.teamPlayerID UTF8String];
|
||||
#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
|
||||
} else {
|
||||
ret["player_id"] = [player.playerID UTF8String];
|
||||
#endif
|
||||
}
|
||||
|
||||
GameCenter::get_singleton()->authenticated = true;
|
||||
} else {
|
||||
ret["result"] = "error";
|
||||
ret["error_code"] = (int64_t)error.code;
|
||||
ret["error_description"] = [error.localizedDescription UTF8String];
|
||||
GameCenter::get_singleton()->authenticated = false;
|
||||
};
|
||||
|
||||
pending_events.push_back(ret);
|
||||
};
|
||||
});
|
||||
|
||||
return OK;
|
||||
};
|
||||
|
||||
bool GameCenter::is_authenticated() {
|
||||
return authenticated;
|
||||
};
|
||||
|
||||
Error GameCenter::post_score(Dictionary p_score) {
|
||||
ERR_FAIL_COND_V(!p_score.has("score") || !p_score.has("category"), ERR_INVALID_PARAMETER);
|
||||
float score = p_score["score"];
|
||||
String category = p_score["category"];
|
||||
|
||||
NSString *cat_str = [[NSString alloc] initWithUTF8String:category.utf8().get_data()];
|
||||
GKScore *reporter = [[GKScore alloc] initWithLeaderboardIdentifier:cat_str];
|
||||
reporter.value = score;
|
||||
|
||||
ERR_FAIL_COND_V([GKScore respondsToSelector:@selector(reportScores)], ERR_UNAVAILABLE);
|
||||
|
||||
[GKScore reportScores:@[ reporter ]
|
||||
withCompletionHandler:^(NSError *error) {
|
||||
Dictionary ret;
|
||||
ret["type"] = "post_score";
|
||||
if (error == nil) {
|
||||
ret["result"] = "ok";
|
||||
} else {
|
||||
ret["result"] = "error";
|
||||
ret["error_code"] = (int64_t)error.code;
|
||||
ret["error_description"] = [error.localizedDescription UTF8String];
|
||||
};
|
||||
|
||||
pending_events.push_back(ret);
|
||||
}];
|
||||
|
||||
return OK;
|
||||
};
|
||||
|
||||
Error GameCenter::award_achievement(Dictionary p_params) {
|
||||
ERR_FAIL_COND_V(!p_params.has("name") || !p_params.has("progress"), ERR_INVALID_PARAMETER);
|
||||
String name = p_params["name"];
|
||||
float progress = p_params["progress"];
|
||||
|
||||
NSString *name_str = [[NSString alloc] initWithUTF8String:name.utf8().get_data()];
|
||||
GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier:name_str];
|
||||
ERR_FAIL_COND_V(!achievement, FAILED);
|
||||
|
||||
ERR_FAIL_COND_V([GKAchievement respondsToSelector:@selector(reportAchievements)], ERR_UNAVAILABLE);
|
||||
|
||||
achievement.percentComplete = progress;
|
||||
achievement.showsCompletionBanner = NO;
|
||||
if (p_params.has("show_completion_banner")) {
|
||||
achievement.showsCompletionBanner = p_params["show_completion_banner"] ? YES : NO;
|
||||
}
|
||||
|
||||
[GKAchievement reportAchievements:@[ achievement ]
|
||||
withCompletionHandler:^(NSError *error) {
|
||||
Dictionary ret;
|
||||
ret["type"] = "award_achievement";
|
||||
if (error == nil) {
|
||||
ret["result"] = "ok";
|
||||
} else {
|
||||
ret["result"] = "error";
|
||||
ret["error_code"] = (int64_t)error.code;
|
||||
};
|
||||
|
||||
pending_events.push_back(ret);
|
||||
}];
|
||||
|
||||
return OK;
|
||||
};
|
||||
|
||||
void GameCenter::request_achievement_descriptions() {
|
||||
[GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:^(NSArray *descriptions, NSError *error) {
|
||||
Dictionary ret;
|
||||
ret["type"] = "achievement_descriptions";
|
||||
if (error == nil) {
|
||||
ret["result"] = "ok";
|
||||
PackedStringArray names;
|
||||
PackedStringArray titles;
|
||||
PackedStringArray unachieved_descriptions;
|
||||
PackedStringArray achieved_descriptions;
|
||||
PackedInt32Array maximum_points;
|
||||
Array hidden;
|
||||
Array replayable;
|
||||
|
||||
for (NSUInteger i = 0; i < [descriptions count]; i++) {
|
||||
GKAchievementDescription *description = [descriptions objectAtIndex:i];
|
||||
|
||||
const char *str = [description.identifier UTF8String];
|
||||
names.push_back(String::utf8(str != NULL ? str : ""));
|
||||
|
||||
str = [description.title UTF8String];
|
||||
titles.push_back(String::utf8(str != NULL ? str : ""));
|
||||
|
||||
str = [description.unachievedDescription UTF8String];
|
||||
unachieved_descriptions.push_back(String::utf8(str != NULL ? str : ""));
|
||||
|
||||
str = [description.achievedDescription UTF8String];
|
||||
achieved_descriptions.push_back(String::utf8(str != NULL ? str : ""));
|
||||
|
||||
maximum_points.push_back(description.maximumPoints);
|
||||
|
||||
hidden.push_back(description.hidden == YES);
|
||||
|
||||
replayable.push_back(description.replayable == YES);
|
||||
}
|
||||
|
||||
ret["names"] = names;
|
||||
ret["titles"] = titles;
|
||||
ret["unachieved_descriptions"] = unachieved_descriptions;
|
||||
ret["achieved_descriptions"] = achieved_descriptions;
|
||||
ret["maximum_points"] = maximum_points;
|
||||
ret["hidden"] = hidden;
|
||||
ret["replayable"] = replayable;
|
||||
|
||||
} else {
|
||||
ret["result"] = "error";
|
||||
ret["error_code"] = (int64_t)error.code;
|
||||
};
|
||||
|
||||
pending_events.push_back(ret);
|
||||
}];
|
||||
};
|
||||
|
||||
void GameCenter::request_achievements() {
|
||||
[GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error) {
|
||||
Dictionary ret;
|
||||
ret["type"] = "achievements";
|
||||
if (error == nil) {
|
||||
ret["result"] = "ok";
|
||||
PackedStringArray names;
|
||||
PackedFloat32Array percentages;
|
||||
|
||||
for (NSUInteger i = 0; i < [achievements count]; i++) {
|
||||
GKAchievement *achievement = [achievements objectAtIndex:i];
|
||||
const char *str = [achievement.identifier UTF8String];
|
||||
names.push_back(String::utf8(str != NULL ? str : ""));
|
||||
|
||||
percentages.push_back(achievement.percentComplete);
|
||||
}
|
||||
|
||||
ret["names"] = names;
|
||||
ret["progress"] = percentages;
|
||||
|
||||
} else {
|
||||
ret["result"] = "error";
|
||||
ret["error_code"] = (int64_t)error.code;
|
||||
};
|
||||
|
||||
pending_events.push_back(ret);
|
||||
}];
|
||||
};
|
||||
|
||||
void GameCenter::reset_achievements() {
|
||||
[GKAchievement resetAchievementsWithCompletionHandler:^(NSError *error) {
|
||||
Dictionary ret;
|
||||
ret["type"] = "reset_achievements";
|
||||
if (error == nil) {
|
||||
ret["result"] = "ok";
|
||||
} else {
|
||||
ret["result"] = "error";
|
||||
ret["error_code"] = (int64_t)error.code;
|
||||
};
|
||||
|
||||
pending_events.push_back(ret);
|
||||
}];
|
||||
};
|
||||
|
||||
Error GameCenter::show_game_center(Dictionary p_params) {
|
||||
ERR_FAIL_COND_V(!NSProtocolFromString(@"GKGameCenterControllerDelegate"), FAILED);
|
||||
|
||||
GKGameCenterViewControllerState view_state = GKGameCenterViewControllerStateDefault;
|
||||
if (p_params.has("view")) {
|
||||
String view_name = p_params["view"];
|
||||
if (view_name == "default") {
|
||||
view_state = GKGameCenterViewControllerStateDefault;
|
||||
} else if (view_name == "leaderboards") {
|
||||
view_state = GKGameCenterViewControllerStateLeaderboards;
|
||||
} else if (view_name == "achievements") {
|
||||
view_state = GKGameCenterViewControllerStateAchievements;
|
||||
} else if (view_name == "challenges") {
|
||||
view_state = GKGameCenterViewControllerStateChallenges;
|
||||
} else {
|
||||
return ERR_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
GKGameCenterViewController *controller = [[GKGameCenterViewController alloc] init];
|
||||
ERR_FAIL_COND_V(!controller, FAILED);
|
||||
|
||||
UIViewController *root_controller = [[UIApplication sharedApplication] delegate].window.rootViewController;
|
||||
ERR_FAIL_COND_V(!root_controller, FAILED);
|
||||
|
||||
controller.gameCenterDelegate = gameCenterDelegate;
|
||||
controller.viewState = view_state;
|
||||
if (view_state == GKGameCenterViewControllerStateLeaderboards) {
|
||||
controller.leaderboardIdentifier = nil;
|
||||
if (p_params.has("leaderboard_name")) {
|
||||
String name = p_params["leaderboard_name"];
|
||||
NSString *name_str = [[NSString alloc] initWithUTF8String:name.utf8().get_data()];
|
||||
controller.leaderboardIdentifier = name_str;
|
||||
}
|
||||
}
|
||||
|
||||
[root_controller presentViewController:controller animated:YES completion:nil];
|
||||
|
||||
return OK;
|
||||
};
|
||||
|
||||
Error GameCenter::request_identity_verification_signature() {
|
||||
ERR_FAIL_COND_V(!is_authenticated(), ERR_UNAUTHORIZED);
|
||||
|
||||
GKLocalPlayer *player = [GKLocalPlayer localPlayer];
|
||||
[player generateIdentityVerificationSignatureWithCompletionHandler:^(NSURL *publicKeyUrl, NSData *signature, NSData *salt, uint64_t timestamp, NSError *error) {
|
||||
Dictionary ret;
|
||||
ret["type"] = "identity_verification_signature";
|
||||
if (error == nil) {
|
||||
ret["result"] = "ok";
|
||||
ret["public_key_url"] = [publicKeyUrl.absoluteString UTF8String];
|
||||
ret["signature"] = [[signature base64EncodedStringWithOptions:0] UTF8String];
|
||||
ret["salt"] = [[salt base64EncodedStringWithOptions:0] UTF8String];
|
||||
ret["timestamp"] = timestamp;
|
||||
if (@available(iOS 13, *)) {
|
||||
ret["player_id"] = [player.teamPlayerID UTF8String];
|
||||
#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
|
||||
} else {
|
||||
ret["player_id"] = [player.playerID UTF8String];
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
ret["result"] = "error";
|
||||
ret["error_code"] = (int64_t)error.code;
|
||||
ret["error_description"] = [error.localizedDescription UTF8String];
|
||||
};
|
||||
|
||||
pending_events.push_back(ret);
|
||||
}];
|
||||
|
||||
return OK;
|
||||
};
|
||||
|
||||
void GameCenter::game_center_closed() {
|
||||
Dictionary ret;
|
||||
ret["type"] = "show_game_center";
|
||||
ret["result"] = "ok";
|
||||
pending_events.push_back(ret);
|
||||
}
|
||||
|
||||
int GameCenter::get_pending_event_count() {
|
||||
return pending_events.size();
|
||||
};
|
||||
|
||||
Variant GameCenter::pop_pending_event() {
|
||||
Variant front = pending_events.front()->get();
|
||||
pending_events.pop_front();
|
||||
|
||||
return front;
|
||||
};
|
||||
|
||||
GameCenter *GameCenter::get_singleton() {
|
||||
return instance;
|
||||
};
|
||||
|
||||
GameCenter::GameCenter() {
|
||||
ERR_FAIL_COND(instance != NULL);
|
||||
instance = this;
|
||||
authenticated = false;
|
||||
|
||||
gameCenterDelegate = [[GodotGameCenterDelegate alloc] init];
|
||||
};
|
||||
|
||||
GameCenter::~GameCenter() {
|
||||
if (gameCenterDelegate) {
|
||||
gameCenterDelegate = nil;
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* game_center_delegate.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#import <GameKit/GameKit.h>
|
||||
|
||||
@interface GodotGameCenterDelegate : NSObject <GKGameCenterControllerDelegate>
|
||||
|
||||
@end
|
@ -1,45 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* game_center_delegate.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#import "game_center_delegate.h"
|
||||
|
||||
#include "game_center.h"
|
||||
|
||||
@implementation GodotGameCenterDelegate
|
||||
|
||||
- (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController {
|
||||
//[gameCenterViewController dismissViewControllerAnimated:YES completion:^{GameCenter::get_singleton()->game_center_closed();}];//version for signaling when overlay is completely gone
|
||||
if (GameCenter::get_singleton()) {
|
||||
GameCenter::get_singleton()->game_center_closed();
|
||||
}
|
||||
[gameCenterViewController dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
@end
|
@ -1,48 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* game_center_module.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "game_center_module.h"
|
||||
|
||||
#include "core/config/engine.h"
|
||||
|
||||
#include "game_center.h"
|
||||
|
||||
GameCenter *game_center;
|
||||
|
||||
void register_gamecenter_types() {
|
||||
game_center = memnew(GameCenter);
|
||||
Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center));
|
||||
}
|
||||
|
||||
void unregister_gamecenter_types() {
|
||||
if (game_center) {
|
||||
memdelete(game_center);
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* game_center_module.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
void register_gamecenter_types();
|
||||
void unregister_gamecenter_types();
|
@ -1,17 +0,0 @@
|
||||
[config]
|
||||
name="GameCenter"
|
||||
binary="gamecenter_lib.a"
|
||||
|
||||
initialization="register_gamecenter_types"
|
||||
deinitialization="unregister_gamecenter_types"
|
||||
|
||||
[dependencies]
|
||||
linked=[]
|
||||
embedded=[]
|
||||
system=["GameKit.framework"]
|
||||
|
||||
capabilities=["gamekit"]
|
||||
|
||||
files=[]
|
||||
|
||||
[plist]
|
@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_icloud = env_modules.Clone()
|
||||
|
||||
# (iOS) Enable module support
|
||||
env_icloud.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
|
||||
|
||||
# (iOS) Build as separate static library
|
||||
modules_sources = []
|
||||
env_icloud.add_source_files(modules_sources, "*.cpp")
|
||||
env_icloud.add_source_files(modules_sources, "*.mm")
|
||||
mod_lib = env_modules.add_library("#bin/libgodot_icloud_module" + env["LIBSUFFIX"], modules_sources)
|
@ -1,6 +0,0 @@
|
||||
def can_build(env, platform):
|
||||
return platform == "iphone"
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
@ -1,17 +0,0 @@
|
||||
[config]
|
||||
name="iCloud"
|
||||
binary="icloud_lib.a"
|
||||
|
||||
initialization="register_icloud_types"
|
||||
deinitialization="unregister_icloud_types"
|
||||
|
||||
[dependencies]
|
||||
linked=[]
|
||||
embedded=[]
|
||||
system=[]
|
||||
|
||||
capabilities=[]
|
||||
|
||||
files=[]
|
||||
|
||||
[plist]
|
@ -1,60 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* icloud.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef ICLOUD_H
|
||||
#define ICLOUD_H
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
|
||||
class ICloud : public Object {
|
||||
GDCLASS(ICloud, Object);
|
||||
|
||||
static ICloud *instance;
|
||||
static void _bind_methods();
|
||||
|
||||
List<Variant> pending_events;
|
||||
|
||||
public:
|
||||
Error remove_key(String p_param);
|
||||
Array set_key_values(Dictionary p_params);
|
||||
Variant get_key_value(String p_param);
|
||||
Error synchronize_key_values();
|
||||
Variant get_all_key_values();
|
||||
|
||||
int get_pending_event_count();
|
||||
Variant pop_pending_event();
|
||||
|
||||
static ICloud *get_singleton();
|
||||
|
||||
ICloud();
|
||||
~ICloud();
|
||||
};
|
||||
|
||||
#endif
|
@ -1,345 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* icloud.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "icloud.h"
|
||||
|
||||
#import "platform/iphone/app_delegate.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
ICloud *ICloud::instance = NULL;
|
||||
|
||||
void ICloud::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("remove_key"), &ICloud::remove_key);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_key_values"), &ICloud::set_key_values);
|
||||
ClassDB::bind_method(D_METHOD("get_key_value"), &ICloud::get_key_value);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("synchronize_key_values"), &ICloud::synchronize_key_values);
|
||||
ClassDB::bind_method(D_METHOD("get_all_key_values"), &ICloud::get_all_key_values);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_pending_event_count"), &ICloud::get_pending_event_count);
|
||||
ClassDB::bind_method(D_METHOD("pop_pending_event"), &ICloud::pop_pending_event);
|
||||
};
|
||||
|
||||
int ICloud::get_pending_event_count() {
|
||||
return pending_events.size();
|
||||
};
|
||||
|
||||
Variant ICloud::pop_pending_event() {
|
||||
Variant front = pending_events.front()->get();
|
||||
pending_events.pop_front();
|
||||
|
||||
return front;
|
||||
};
|
||||
|
||||
ICloud *ICloud::get_singleton() {
|
||||
return instance;
|
||||
};
|
||||
|
||||
//convert from apple's abstract type to godot's abstract type....
|
||||
Variant nsobject_to_variant(NSObject *object) {
|
||||
if ([object isKindOfClass:[NSString class]]) {
|
||||
const char *str = [(NSString *)object UTF8String];
|
||||
return String::utf8(str != NULL ? str : "");
|
||||
} else if ([object isKindOfClass:[NSData class]]) {
|
||||
PackedByteArray ret;
|
||||
NSData *data = (NSData *)object;
|
||||
if ([data length] > 0) {
|
||||
ret.resize([data length]);
|
||||
{
|
||||
// PackedByteArray::Write w = ret.write();
|
||||
copymem((void *)ret.ptr(), [data bytes], [data length]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
} else if ([object isKindOfClass:[NSArray class]]) {
|
||||
Array result;
|
||||
NSArray *array = (NSArray *)object;
|
||||
for (NSUInteger i = 0; i < [array count]; ++i) {
|
||||
NSObject *value = [array objectAtIndex:i];
|
||||
result.push_back(nsobject_to_variant(value));
|
||||
}
|
||||
return result;
|
||||
} else if ([object isKindOfClass:[NSDictionary class]]) {
|
||||
Dictionary result;
|
||||
NSDictionary *dic = (NSDictionary *)object;
|
||||
|
||||
NSArray *keys = [dic allKeys];
|
||||
int count = [keys count];
|
||||
for (int i = 0; i < count; ++i) {
|
||||
NSObject *k = [keys objectAtIndex:i];
|
||||
NSObject *v = [dic objectForKey:k];
|
||||
|
||||
result[nsobject_to_variant(k)] = nsobject_to_variant(v);
|
||||
}
|
||||
return result;
|
||||
} else if ([object isKindOfClass:[NSNumber class]]) {
|
||||
//Every type except numbers can reliably identify its type. The following is comparing to the *internal* representation, which isn't guaranteed to match the type that was used to create it, and is not advised, particularly when dealing with potential platform differences (ie, 32/64 bit)
|
||||
//To avoid errors, we'll cast as broadly as possible, and only return int or float.
|
||||
//bool, char, int, uint, longlong -> int
|
||||
//float, double -> float
|
||||
NSNumber *num = (NSNumber *)object;
|
||||
if (strcmp([num objCType], @encode(BOOL)) == 0) {
|
||||
return Variant((int)[num boolValue]);
|
||||
} else if (strcmp([num objCType], @encode(char)) == 0) {
|
||||
return Variant((int)[num charValue]);
|
||||
} else if (strcmp([num objCType], @encode(int)) == 0) {
|
||||
return Variant([num intValue]);
|
||||
} else if (strcmp([num objCType], @encode(unsigned int)) == 0) {
|
||||
return Variant((int)[num unsignedIntValue]);
|
||||
} else if (strcmp([num objCType], @encode(long long)) == 0) {
|
||||
return Variant((int)[num longValue]);
|
||||
} else if (strcmp([num objCType], @encode(float)) == 0) {
|
||||
return Variant([num floatValue]);
|
||||
} else if (strcmp([num objCType], @encode(double)) == 0) {
|
||||
return Variant((float)[num doubleValue]);
|
||||
} else {
|
||||
return Variant();
|
||||
}
|
||||
} else if ([object isKindOfClass:[NSDate class]]) {
|
||||
//this is a type that icloud supports...but how did you submit it in the first place?
|
||||
//I guess this is a type that *might* show up, if you were, say, trying to make your game
|
||||
//compatible with existing cloud data written by another engine's version of your game
|
||||
WARN_PRINT("NSDate unsupported, returning null Variant");
|
||||
return Variant();
|
||||
} else if ([object isKindOfClass:[NSNull class]] or object == nil) {
|
||||
return Variant();
|
||||
} else {
|
||||
WARN_PRINT("Trying to convert unknown NSObject type to Variant");
|
||||
return Variant();
|
||||
}
|
||||
}
|
||||
|
||||
NSObject *variant_to_nsobject(Variant v) {
|
||||
if (v.get_type() == Variant::STRING) {
|
||||
return [[NSString alloc] initWithUTF8String:((String)v).utf8().get_data()];
|
||||
} else if (v.get_type() == Variant::FLOAT) {
|
||||
return [NSNumber numberWithDouble:(double)v];
|
||||
} else if (v.get_type() == Variant::INT) {
|
||||
return [NSNumber numberWithLongLong:(long)(int)v];
|
||||
} else if (v.get_type() == Variant::BOOL) {
|
||||
return [NSNumber numberWithBool:BOOL((bool)v)];
|
||||
} else if (v.get_type() == Variant::DICTIONARY) {
|
||||
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
|
||||
Dictionary dic = v;
|
||||
Array keys = dic.keys();
|
||||
for (int i = 0; i < keys.size(); ++i) {
|
||||
NSString *key = [[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()];
|
||||
NSObject *value = variant_to_nsobject(dic[keys[i]]);
|
||||
|
||||
if (key == NULL || value == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
[result setObject:value forKey:key];
|
||||
}
|
||||
return result;
|
||||
} else if (v.get_type() == Variant::ARRAY) {
|
||||
NSMutableArray *result = [[NSMutableArray alloc] init];
|
||||
Array arr = v;
|
||||
for (int i = 0; i < arr.size(); ++i) {
|
||||
NSObject *value = variant_to_nsobject(arr[i]);
|
||||
if (value == NULL) {
|
||||
//trying to add something unsupported to the array. cancel the whole array
|
||||
return NULL;
|
||||
}
|
||||
[result addObject:value];
|
||||
}
|
||||
return result;
|
||||
} else if (v.get_type() == Variant::PACKED_BYTE_ARRAY) {
|
||||
PackedByteArray arr = v;
|
||||
// PackedByteArray::Read r = arr.read();
|
||||
NSData *result = [NSData dataWithBytes:arr.ptr() length:arr.size()];
|
||||
return result;
|
||||
}
|
||||
WARN_PRINT(String("Could not add unsupported type to iCloud: '" + Variant::get_type_name(v.get_type()) + "'").utf8().get_data());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Error ICloud::remove_key(String p_param) {
|
||||
NSString *key = [[NSString alloc] initWithUTF8String:p_param.utf8().get_data()];
|
||||
|
||||
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
|
||||
|
||||
if (![[store dictionaryRepresentation] objectForKey:key]) {
|
||||
return ERR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
[store removeObjectForKey:key];
|
||||
return OK;
|
||||
}
|
||||
|
||||
//return an array of the keys that could not be set
|
||||
Array ICloud::set_key_values(Dictionary p_params) {
|
||||
Array keys = p_params.keys();
|
||||
|
||||
Array error_keys;
|
||||
|
||||
for (int i = 0; i < keys.size(); ++i) {
|
||||
String variant_key = keys[i];
|
||||
Variant variant_value = p_params[variant_key];
|
||||
|
||||
NSString *key = [[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()];
|
||||
if (key == NULL) {
|
||||
error_keys.push_back(variant_key);
|
||||
continue;
|
||||
}
|
||||
|
||||
NSObject *value = variant_to_nsobject(variant_value);
|
||||
|
||||
if (value == NULL) {
|
||||
error_keys.push_back(variant_key);
|
||||
continue;
|
||||
}
|
||||
|
||||
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
|
||||
[store setObject:value forKey:key];
|
||||
}
|
||||
|
||||
return error_keys;
|
||||
}
|
||||
|
||||
Variant ICloud::get_key_value(String p_param) {
|
||||
NSString *key = [[NSString alloc] initWithUTF8String:p_param.utf8().get_data()];
|
||||
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
|
||||
|
||||
if (![[store dictionaryRepresentation] objectForKey:key]) {
|
||||
return Variant();
|
||||
}
|
||||
|
||||
Variant result = nsobject_to_variant([[store dictionaryRepresentation] objectForKey:key]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Variant ICloud::get_all_key_values() {
|
||||
Dictionary result;
|
||||
|
||||
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
|
||||
NSDictionary *store_dictionary = [store dictionaryRepresentation];
|
||||
|
||||
NSArray *keys = [store_dictionary allKeys];
|
||||
int count = [keys count];
|
||||
for (int i = 0; i < count; ++i) {
|
||||
NSString *k = [keys objectAtIndex:i];
|
||||
NSObject *v = [store_dictionary objectForKey:k];
|
||||
|
||||
const char *str = [k UTF8String];
|
||||
if (str != NULL) {
|
||||
result[String::utf8(str)] = nsobject_to_variant(v);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Error ICloud::synchronize_key_values() {
|
||||
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
|
||||
BOOL result = [store synchronize];
|
||||
if (result == YES) {
|
||||
return OK;
|
||||
} else {
|
||||
return FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Error ICloud::initial_sync() {
|
||||
//you sometimes have to write something to the store to get it to download new data. go apple!
|
||||
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
|
||||
if ([store boolForKey:@"isb"])
|
||||
{
|
||||
[store setBool:NO forKey:@"isb"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[store setBool:YES forKey:@"isb"];
|
||||
}
|
||||
return synchronize();
|
||||
}
|
||||
|
||||
*/
|
||||
ICloud::ICloud() {
|
||||
ERR_FAIL_COND(instance != NULL);
|
||||
instance = this;
|
||||
//connected = false;
|
||||
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
|
||||
object:[NSUbiquitousKeyValueStore defaultStore]
|
||||
queue:nil
|
||||
usingBlock:^(NSNotification *notification) {
|
||||
NSDictionary *userInfo = [notification userInfo];
|
||||
NSInteger change = [[userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey] integerValue];
|
||||
|
||||
Dictionary ret;
|
||||
ret["type"] = "key_value_changed";
|
||||
|
||||
//PackedStringArray result_keys;
|
||||
//Array result_values;
|
||||
Dictionary keyValues;
|
||||
String reason = "";
|
||||
|
||||
if (change == NSUbiquitousKeyValueStoreServerChange) {
|
||||
reason = "server";
|
||||
} else if (change == NSUbiquitousKeyValueStoreInitialSyncChange) {
|
||||
reason = "initial_sync";
|
||||
} else if (change == NSUbiquitousKeyValueStoreQuotaViolationChange) {
|
||||
reason = "quota_violation";
|
||||
} else if (change == NSUbiquitousKeyValueStoreAccountChange) {
|
||||
reason = "account";
|
||||
}
|
||||
|
||||
ret["reason"] = reason;
|
||||
|
||||
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
|
||||
|
||||
NSArray *keys = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
|
||||
for (NSString *key in keys) {
|
||||
const char *str = [key UTF8String];
|
||||
if (str == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NSObject *object = [store objectForKey:key];
|
||||
|
||||
//figure out what kind of object it is
|
||||
Variant value = nsobject_to_variant(object);
|
||||
|
||||
keyValues[String::utf8(str)] = value;
|
||||
}
|
||||
|
||||
ret["changed_values"] = keyValues;
|
||||
pending_events.push_back(ret);
|
||||
}];
|
||||
}
|
||||
|
||||
ICloud::~ICloud() {}
|
@ -1,48 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* icloud_module.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "icloud_module.h"
|
||||
|
||||
#include "core/config/engine.h"
|
||||
|
||||
#include "icloud.h"
|
||||
|
||||
ICloud *icloud;
|
||||
|
||||
void register_icloud_types() {
|
||||
icloud = memnew(ICloud);
|
||||
Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud));
|
||||
}
|
||||
|
||||
void unregister_icloud_types() {
|
||||
if (icloud) {
|
||||
memdelete(icloud);
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* icloud_module.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
void register_icloud_types();
|
||||
void unregister_icloud_types();
|
@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_inappstore = env_modules.Clone()
|
||||
|
||||
# (iOS) Enable module support
|
||||
env_inappstore.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
|
||||
|
||||
# (iOS) Build as separate static library
|
||||
modules_sources = []
|
||||
env_inappstore.add_source_files(modules_sources, "*.cpp")
|
||||
env_inappstore.add_source_files(modules_sources, "*.mm")
|
||||
mod_lib = env_modules.add_library("#bin/libgodot_inappstore_module" + env["LIBSUFFIX"], modules_sources)
|
@ -1,6 +0,0 @@
|
||||
def can_build(env, platform):
|
||||
return platform == "iphone"
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
@ -1,77 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* in_app_store.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef IN_APP_STORE_H
|
||||
#define IN_APP_STORE_H
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
|
||||
#ifdef __OBJC__
|
||||
@class GodotProductsDelegate;
|
||||
@class GodotTransactionsObserver;
|
||||
|
||||
typedef GodotProductsDelegate InAppStoreProductDelegate;
|
||||
typedef GodotTransactionsObserver InAppStoreTransactionObserver;
|
||||
#else
|
||||
typedef void InAppStoreProductDelegate;
|
||||
typedef void InAppStoreTransactionObserver;
|
||||
#endif
|
||||
|
||||
class InAppStore : public Object {
|
||||
GDCLASS(InAppStore, Object);
|
||||
|
||||
static InAppStore *instance;
|
||||
static void _bind_methods();
|
||||
|
||||
List<Variant> pending_events;
|
||||
|
||||
InAppStoreProductDelegate *products_request_delegate;
|
||||
InAppStoreTransactionObserver *transactions_observer;
|
||||
|
||||
public:
|
||||
Error request_product_info(Dictionary p_params);
|
||||
Error restore_purchases();
|
||||
Error purchase(Dictionary p_params);
|
||||
|
||||
int get_pending_event_count();
|
||||
Variant pop_pending_event();
|
||||
void finish_transaction(String product_id);
|
||||
void set_auto_finish_transaction(bool b);
|
||||
|
||||
void _post_event(Variant p_event);
|
||||
void _record_purchase(String product_id);
|
||||
|
||||
static InAppStore *get_singleton();
|
||||
|
||||
InAppStore();
|
||||
~InAppStore();
|
||||
};
|
||||
|
||||
#endif
|
@ -1,411 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* in_app_store.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "in_app_store.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <StoreKit/StoreKit.h>
|
||||
|
||||
InAppStore *InAppStore::instance = NULL;
|
||||
|
||||
@interface SKProduct (LocalizedPrice)
|
||||
|
||||
@property(nonatomic, readonly) NSString *localizedPrice;
|
||||
|
||||
@end
|
||||
|
||||
//----------------------------------//
|
||||
// SKProduct extension
|
||||
//----------------------------------//
|
||||
@implementation SKProduct (LocalizedPrice)
|
||||
|
||||
- (NSString *)localizedPrice {
|
||||
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
|
||||
[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
|
||||
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
|
||||
[numberFormatter setLocale:self.priceLocale];
|
||||
NSString *formattedString = [numberFormatter stringFromNumber:self.price];
|
||||
return formattedString;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface GodotProductsDelegate : NSObject <SKProductsRequestDelegate>
|
||||
|
||||
@property(nonatomic, strong) NSMutableArray *loadedProducts;
|
||||
@property(nonatomic, strong) NSMutableArray *pendingRequests;
|
||||
|
||||
- (void)performRequestWithProductIDs:(NSSet *)productIDs;
|
||||
- (Error)purchaseProductWithProductID:(NSString *)productID;
|
||||
- (void)reset;
|
||||
|
||||
@end
|
||||
|
||||
@implementation GodotProductsDelegate
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
[self godot_commonInit];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)godot_commonInit {
|
||||
self.loadedProducts = [NSMutableArray new];
|
||||
self.pendingRequests = [NSMutableArray new];
|
||||
}
|
||||
|
||||
- (void)performRequestWithProductIDs:(NSSet *)productIDs {
|
||||
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:productIDs];
|
||||
|
||||
request.delegate = self;
|
||||
[self.pendingRequests addObject:request];
|
||||
[request start];
|
||||
}
|
||||
|
||||
- (Error)purchaseProductWithProductID:(NSString *)productID {
|
||||
SKProduct *product = nil;
|
||||
|
||||
NSLog(@"searching for product!");
|
||||
|
||||
if (self.loadedProducts) {
|
||||
for (SKProduct *storedProduct in self.loadedProducts) {
|
||||
if ([storedProduct.productIdentifier isEqualToString:productID]) {
|
||||
product = storedProduct;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!product) {
|
||||
return ERR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
NSLog(@"product found!");
|
||||
|
||||
SKPayment *payment = [SKPayment paymentWithProduct:product];
|
||||
[[SKPaymentQueue defaultQueue] addPayment:payment];
|
||||
|
||||
NSLog(@"purchase sent!");
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
[self.loadedProducts removeAllObjects];
|
||||
[self.pendingRequests removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
|
||||
[self.pendingRequests removeObject:request];
|
||||
|
||||
Dictionary ret;
|
||||
ret["type"] = "product_info";
|
||||
ret["result"] = "error";
|
||||
ret["error"] = String::utf8([error.localizedDescription UTF8String]);
|
||||
|
||||
InAppStore::get_singleton()->_post_event(ret);
|
||||
}
|
||||
|
||||
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
|
||||
[self.pendingRequests removeObject:request];
|
||||
|
||||
NSArray *products = response.products;
|
||||
[self.loadedProducts addObjectsFromArray:products];
|
||||
|
||||
Dictionary ret;
|
||||
ret["type"] = "product_info";
|
||||
ret["result"] = "ok";
|
||||
PackedStringArray titles;
|
||||
PackedStringArray descriptions;
|
||||
PackedFloat32Array prices;
|
||||
PackedStringArray ids;
|
||||
PackedStringArray localized_prices;
|
||||
PackedStringArray currency_codes;
|
||||
|
||||
for (NSUInteger i = 0; i < [products count]; i++) {
|
||||
SKProduct *product = [products objectAtIndex:i];
|
||||
|
||||
const char *str = [product.localizedTitle UTF8String];
|
||||
titles.push_back(String::utf8(str != NULL ? str : ""));
|
||||
|
||||
str = [product.localizedDescription UTF8String];
|
||||
descriptions.push_back(String::utf8(str != NULL ? str : ""));
|
||||
prices.push_back([product.price doubleValue]);
|
||||
ids.push_back(String::utf8([product.productIdentifier UTF8String]));
|
||||
localized_prices.push_back(String::utf8([product.localizedPrice UTF8String]));
|
||||
currency_codes.push_back(String::utf8([[[product priceLocale] objectForKey:NSLocaleCurrencyCode] UTF8String]));
|
||||
}
|
||||
|
||||
ret["titles"] = titles;
|
||||
ret["descriptions"] = descriptions;
|
||||
ret["prices"] = prices;
|
||||
ret["ids"] = ids;
|
||||
ret["localized_prices"] = localized_prices;
|
||||
ret["currency_codes"] = currency_codes;
|
||||
|
||||
PackedStringArray invalid_ids;
|
||||
|
||||
for (NSString *ipid in response.invalidProductIdentifiers) {
|
||||
invalid_ids.push_back(String::utf8([ipid UTF8String]));
|
||||
}
|
||||
|
||||
ret["invalid_ids"] = invalid_ids;
|
||||
|
||||
InAppStore::get_singleton()->_post_event(ret);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface GodotTransactionsObserver : NSObject <SKPaymentTransactionObserver>
|
||||
|
||||
@property(nonatomic, assign) BOOL shouldAutoFinishTransactions;
|
||||
@property(nonatomic, strong) NSMutableDictionary *pendingTransactions;
|
||||
|
||||
- (void)finishTransactionWithProductID:(NSString *)productID;
|
||||
- (void)reset;
|
||||
|
||||
@end
|
||||
|
||||
@implementation GodotTransactionsObserver
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
[self godot_commonInit];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)godot_commonInit {
|
||||
self.pendingTransactions = [NSMutableDictionary new];
|
||||
}
|
||||
|
||||
- (void)finishTransactionWithProductID:(NSString *)productID {
|
||||
SKPaymentTransaction *transaction = self.pendingTransactions[productID];
|
||||
|
||||
if (transaction) {
|
||||
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
||||
}
|
||||
|
||||
self.pendingTransactions[productID] = nil;
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
[self.pendingTransactions removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
|
||||
printf("transactions updated!\n");
|
||||
for (SKPaymentTransaction *transaction in transactions) {
|
||||
switch (transaction.transactionState) {
|
||||
case SKPaymentTransactionStatePurchased: {
|
||||
printf("status purchased!\n");
|
||||
String pid = String::utf8([transaction.payment.productIdentifier UTF8String]);
|
||||
String transactionId = String::utf8([transaction.transactionIdentifier UTF8String]);
|
||||
InAppStore::get_singleton()->_record_purchase(pid);
|
||||
Dictionary ret;
|
||||
ret["type"] = "purchase";
|
||||
ret["result"] = "ok";
|
||||
ret["product_id"] = pid;
|
||||
ret["transaction_id"] = transactionId;
|
||||
|
||||
NSData *receipt = nil;
|
||||
int sdk_version = [[[UIDevice currentDevice] systemVersion] intValue];
|
||||
|
||||
NSBundle *bundle = [NSBundle mainBundle];
|
||||
// Get the transaction receipt file path location in the app bundle.
|
||||
NSURL *receiptFileURL = [bundle appStoreReceiptURL];
|
||||
|
||||
// Read in the contents of the transaction file.
|
||||
receipt = [NSData dataWithContentsOfURL:receiptFileURL];
|
||||
|
||||
NSString *receipt_to_send = nil;
|
||||
|
||||
if (receipt != nil) {
|
||||
receipt_to_send = [receipt base64EncodedStringWithOptions:0];
|
||||
}
|
||||
Dictionary receipt_ret;
|
||||
receipt_ret["receipt"] = String::utf8(receipt_to_send != nil ? [receipt_to_send UTF8String] : "");
|
||||
receipt_ret["sdk"] = sdk_version;
|
||||
ret["receipt"] = receipt_ret;
|
||||
|
||||
InAppStore::get_singleton()->_post_event(ret);
|
||||
|
||||
if (self.shouldAutoFinishTransactions) {
|
||||
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
||||
} else {
|
||||
self.pendingTransactions[transaction.payment.productIdentifier] = transaction;
|
||||
}
|
||||
|
||||
} break;
|
||||
case SKPaymentTransactionStateFailed: {
|
||||
printf("status transaction failed!\n");
|
||||
String pid = String::utf8([transaction.payment.productIdentifier UTF8String]);
|
||||
Dictionary ret;
|
||||
ret["type"] = "purchase";
|
||||
ret["result"] = "error";
|
||||
ret["product_id"] = pid;
|
||||
ret["error"] = String::utf8([transaction.error.localizedDescription UTF8String]);
|
||||
InAppStore::get_singleton()->_post_event(ret);
|
||||
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
||||
} break;
|
||||
case SKPaymentTransactionStateRestored: {
|
||||
printf("status transaction restored!\n");
|
||||
String pid = String::utf8([transaction.originalTransaction.payment.productIdentifier UTF8String]);
|
||||
InAppStore::get_singleton()->_record_purchase(pid);
|
||||
Dictionary ret;
|
||||
ret["type"] = "restore";
|
||||
ret["result"] = "ok";
|
||||
ret["product_id"] = pid;
|
||||
InAppStore::get_singleton()->_post_event(ret);
|
||||
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
||||
} break;
|
||||
default: {
|
||||
printf("status default %i!\n", (int)transaction.transactionState);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
void InAppStore::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("request_product_info"), &InAppStore::request_product_info);
|
||||
ClassDB::bind_method(D_METHOD("restore_purchases"), &InAppStore::restore_purchases);
|
||||
ClassDB::bind_method(D_METHOD("purchase"), &InAppStore::purchase);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_pending_event_count"), &InAppStore::get_pending_event_count);
|
||||
ClassDB::bind_method(D_METHOD("pop_pending_event"), &InAppStore::pop_pending_event);
|
||||
ClassDB::bind_method(D_METHOD("finish_transaction"), &InAppStore::finish_transaction);
|
||||
ClassDB::bind_method(D_METHOD("set_auto_finish_transaction"), &InAppStore::set_auto_finish_transaction);
|
||||
}
|
||||
|
||||
Error InAppStore::request_product_info(Dictionary p_params) {
|
||||
ERR_FAIL_COND_V(!p_params.has("product_ids"), ERR_INVALID_PARAMETER);
|
||||
|
||||
PackedStringArray pids = p_params["product_ids"];
|
||||
printf("************ request product info! %i\n", pids.size());
|
||||
|
||||
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:pids.size()];
|
||||
for (int i = 0; i < pids.size(); i++) {
|
||||
printf("******** adding %s to product list\n", pids[i].utf8().get_data());
|
||||
NSString *pid = [[NSString alloc] initWithUTF8String:pids[i].utf8().get_data()];
|
||||
[array addObject:pid];
|
||||
};
|
||||
|
||||
NSSet *products = [[NSSet alloc] initWithArray:array];
|
||||
|
||||
[products_request_delegate performRequestWithProductIDs:products];
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error InAppStore::restore_purchases() {
|
||||
printf("restoring purchases!\n");
|
||||
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error InAppStore::purchase(Dictionary p_params) {
|
||||
ERR_FAIL_COND_V(![SKPaymentQueue canMakePayments], ERR_UNAVAILABLE);
|
||||
if (![SKPaymentQueue canMakePayments]) {
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
printf("purchasing!\n");
|
||||
Dictionary params = p_params;
|
||||
ERR_FAIL_COND_V(!params.has("product_id"), ERR_INVALID_PARAMETER);
|
||||
|
||||
NSString *pid = [[NSString alloc] initWithUTF8String:String(params["product_id"]).utf8().get_data()];
|
||||
|
||||
return [products_request_delegate purchaseProductWithProductID:pid];
|
||||
}
|
||||
|
||||
int InAppStore::get_pending_event_count() {
|
||||
return pending_events.size();
|
||||
}
|
||||
|
||||
Variant InAppStore::pop_pending_event() {
|
||||
Variant front = pending_events.front()->get();
|
||||
pending_events.pop_front();
|
||||
|
||||
return front;
|
||||
}
|
||||
|
||||
void InAppStore::_post_event(Variant p_event) {
|
||||
pending_events.push_back(p_event);
|
||||
}
|
||||
|
||||
void InAppStore::_record_purchase(String product_id) {
|
||||
String skey = "purchased/" + product_id;
|
||||
NSString *key = [[NSString alloc] initWithUTF8String:skey.utf8().get_data()];
|
||||
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:key];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
}
|
||||
|
||||
InAppStore *InAppStore::get_singleton() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
InAppStore::InAppStore() {
|
||||
ERR_FAIL_COND(instance != NULL);
|
||||
instance = this;
|
||||
|
||||
products_request_delegate = [[GodotProductsDelegate alloc] init];
|
||||
transactions_observer = [[GodotTransactionsObserver alloc] init];
|
||||
|
||||
[[SKPaymentQueue defaultQueue] addTransactionObserver:transactions_observer];
|
||||
}
|
||||
|
||||
void InAppStore::finish_transaction(String product_id) {
|
||||
NSString *prod_id = [NSString stringWithCString:product_id.utf8().get_data() encoding:NSUTF8StringEncoding];
|
||||
|
||||
[transactions_observer finishTransactionWithProductID:prod_id];
|
||||
}
|
||||
|
||||
void InAppStore::set_auto_finish_transaction(bool b) {
|
||||
transactions_observer.shouldAutoFinishTransactions = b;
|
||||
}
|
||||
|
||||
InAppStore::~InAppStore() {
|
||||
[products_request_delegate reset];
|
||||
[transactions_observer reset];
|
||||
|
||||
products_request_delegate = nil;
|
||||
[[SKPaymentQueue defaultQueue] removeTransactionObserver:transactions_observer];
|
||||
transactions_observer = nil;
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* in_app_store_module.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "in_app_store_module.h"
|
||||
|
||||
#include "core/config/engine.h"
|
||||
|
||||
#include "in_app_store.h"
|
||||
|
||||
InAppStore *store_kit;
|
||||
|
||||
void register_inappstore_types() {
|
||||
store_kit = memnew(InAppStore);
|
||||
Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit));
|
||||
}
|
||||
|
||||
void unregister_inappstore_types() {
|
||||
if (store_kit) {
|
||||
memdelete(store_kit);
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
/*************************************************************************/
|
||||
/* in_app_store_module.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
void register_inappstore_types();
|
||||
void unregister_inappstore_types();
|
@ -1,17 +0,0 @@
|
||||
[config]
|
||||
name="InAppStore"
|
||||
binary="inappstore_lib.a"
|
||||
|
||||
initialization="register_inappstore_types"
|
||||
deinitialization="unregister_inappstore_types"
|
||||
|
||||
[dependencies]
|
||||
linked=[]
|
||||
embedded=[]
|
||||
system=["StoreKit.framework"]
|
||||
|
||||
capabilities=[]
|
||||
|
||||
files=[]
|
||||
|
||||
[plist]
|
@ -188,38 +188,6 @@ def configure(env):
|
||||
LINKFLAGS=[
|
||||
"-isysroot",
|
||||
"$IPHONESDK",
|
||||
"-framework",
|
||||
"AudioToolbox",
|
||||
"-framework",
|
||||
"AVFoundation",
|
||||
"-framework",
|
||||
"CoreAudio",
|
||||
"-framework",
|
||||
"CoreGraphics",
|
||||
"-framework",
|
||||
"CoreMedia",
|
||||
"-framework",
|
||||
"CoreVideo",
|
||||
"-framework",
|
||||
"CoreMotion",
|
||||
"-framework",
|
||||
"Foundation",
|
||||
"-framework",
|
||||
"GameController",
|
||||
"-framework",
|
||||
"MediaPlayer",
|
||||
"-framework",
|
||||
"Metal",
|
||||
"-framework",
|
||||
"QuartzCore",
|
||||
"-framework",
|
||||
"Security",
|
||||
"-framework",
|
||||
"SystemConfiguration",
|
||||
"-framework",
|
||||
"UIKit",
|
||||
"-framework",
|
||||
"ARKit",
|
||||
]
|
||||
)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user