add file picking for osx and ios and implement equirectangular import
This commit is contained in:
@@ -26,6 +26,29 @@
|
||||
{
|
||||
CGLFlushDrawable([glctx CGLContextObj]);
|
||||
}
|
||||
- (std::string)pick_file
|
||||
{
|
||||
NSOpenPanel *panel = [NSOpenPanel openPanel];
|
||||
[panel setCanChooseFiles:YES];
|
||||
[panel setCanChooseDirectories:NO];
|
||||
[panel setAllowsMultipleSelection:NO]; // yes if more than one dir is allowed
|
||||
|
||||
NSArray* fileTypes = [NSArray arrayWithObjects:@"png", @"PNG", @"jpg", @"JPG", @"jpeg", nil];
|
||||
[panel setAllowedFileTypes:fileTypes];
|
||||
|
||||
NSInteger clicked = [panel runModal];
|
||||
|
||||
std::string ret;
|
||||
if (clicked == NSFileHandlingPanelOKButton)
|
||||
{
|
||||
for (NSURL *url in [panel URLs])
|
||||
{
|
||||
LOG("selected file: %s", [[url path] cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
ret = [[url path] cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect
|
||||
{
|
||||
gl_ready = false;
|
||||
|
||||
@@ -14,4 +14,5 @@
|
||||
- (void)async_lock;
|
||||
- (void)async_unlock;
|
||||
- (void)async_swap;
|
||||
- (std::string)pick_file;
|
||||
@end
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <GLKit/GLKit.h>
|
||||
|
||||
@interface GameViewController : GLKViewController <UIKeyInput>
|
||||
@interface GameViewController : GLKViewController <UIKeyInput,UIImagePickerControllerDelegate,UINavigationControllerDelegate>
|
||||
{
|
||||
@public GLKView* glview;
|
||||
}
|
||||
@@ -17,5 +17,7 @@
|
||||
- (void)async_lock;
|
||||
- (void)async_unlock;
|
||||
- (void)async_swap;
|
||||
|
||||
- (void)pick_photo:(std::function<void(std::string)>) callback;
|
||||
- (void)registerForKeyboardNotifications;
|
||||
- (void)unregisterForKeyboardNotifications;
|
||||
@end
|
||||
|
||||
@@ -11,6 +11,15 @@
|
||||
#import <OpenGLES/ES3/glext.h>
|
||||
#include "app.h"
|
||||
|
||||
@interface GameImagePicker : UIImagePickerController
|
||||
{
|
||||
@public std::promise<std::string> promise;
|
||||
@public std::function<void(std::string)> callback;
|
||||
}
|
||||
@end
|
||||
@implementation GameImagePicker
|
||||
@end
|
||||
|
||||
@interface GameViewController () {
|
||||
NSLock* gl_lock;
|
||||
}
|
||||
@@ -48,6 +57,33 @@ NSThread* lock_thread;
|
||||
[self.context presentRenderbuffer:GL_RENDERBUFFER];
|
||||
}
|
||||
|
||||
- (void)pick_photo:(std::function<void(std::string)>) callback
|
||||
{
|
||||
GameImagePicker *picker = [[GameImagePicker alloc] init];
|
||||
picker.delegate = self;
|
||||
picker.allowsEditing = NO;
|
||||
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
|
||||
picker->callback = callback;
|
||||
[self presentViewController:picker animated:YES completion:NULL];
|
||||
}
|
||||
|
||||
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
|
||||
{
|
||||
NSString *chosenImage = [info[UIImagePickerControllerImageURL] path];
|
||||
GameImagePicker* p = static_cast<GameImagePicker*>(picker);
|
||||
std::string path = [chosenImage cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
p->callback(path);
|
||||
[picker dismissViewControllerAnimated:YES completion:^{
|
||||
[self keyboardWillBeHidden:nil];
|
||||
}];
|
||||
}
|
||||
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
|
||||
{
|
||||
[picker dismissViewControllerAnimated:YES completion:^{
|
||||
[self keyboardWillBeHidden:nil];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)insertText:(NSString *)text
|
||||
{
|
||||
if (const char* cstr = [text cStringUsingEncoding:NSASCIIStringEncoding])
|
||||
@@ -84,6 +120,18 @@ NSThread* lock_thread;
|
||||
selector:@selector(keyboardWasHidden:)
|
||||
name:UIKeyboardDidHideNotification object:nil];
|
||||
}
|
||||
- (void)unregisterForKeyboardNotifications
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UIKeyboardWillShowNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UIKeyboardWillHideNotification object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UIKeyboardDidShowNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UIKeyboardDidHideNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)keyboardWillBeShown:(NSNotification*)aNotification
|
||||
{
|
||||
@@ -94,6 +142,7 @@ NSThread* lock_thread;
|
||||
{
|
||||
App::I.redraw = true;
|
||||
App::I.animate = false;
|
||||
[self unregisterForKeyboardNotifications];
|
||||
}
|
||||
|
||||
// Called when the UIKeyboardDidShowNotification is sent.
|
||||
@@ -233,7 +282,7 @@ NSThread* lock_thread;
|
||||
{
|
||||
App::I.redraw = true;
|
||||
[self resignFirstResponder];
|
||||
[self registerForKeyboardNotifications];
|
||||
//[self registerForKeyboardNotifications];
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
|
||||
@@ -531,6 +531,10 @@
|
||||
<icon icon="page_add" width="20"/>
|
||||
<text text="New Pano" margin="0 0 0 5" font-face="arial" font-size="11"/>
|
||||
</button-custom>
|
||||
<button-custom id="file-import" height="40" align="center" color=".2" pad="0 0 0 10" dir="row">
|
||||
<icon icon="page_add" width="20"/>
|
||||
<text text="Import" margin="0 0 0 5" font-face="arial" font-size="11"/>
|
||||
</button-custom>
|
||||
<button-custom id="file-open" text="Menu" height="40" align="center" color=".2" pad="0 0 0 10" dir="row">
|
||||
<icon icon="page_add" width="20"/>
|
||||
<text text="Open" margin="0 0 0 5" font-face="arial" font-size="11"/>
|
||||
|
||||
@@ -259,8 +259,8 @@ void App::update(float dt)
|
||||
stroke->update_controls();
|
||||
auto pix = ui::Canvas::I->m_current_brush.m_tip_color;
|
||||
auto hsv = convert_rgb2hsv(glm::vec3(pix[0], pix[1], pix[2]));
|
||||
color->m_hue->m_value.y = hsv.x;
|
||||
color->m_quad->m_value = glm::vec2(hsv.y, 1.f - hsv.z);
|
||||
//color->m_hue->m_value.y = hsv.x;
|
||||
//color->m_quad->m_value = glm::vec2(hsv.y, 1.f - hsv.z);
|
||||
|
||||
auto observer = [this](Node* n)
|
||||
{
|
||||
|
||||
@@ -82,6 +82,7 @@ public:
|
||||
struct android_app* and_app;
|
||||
struct engine* and_engine;
|
||||
#endif
|
||||
void pick_image(std::function<void(std::string path)> callback);
|
||||
void showKeyboard();
|
||||
void hideKeyboard();
|
||||
void initLog();
|
||||
|
||||
@@ -22,6 +22,7 @@ void App::showKeyboard()
|
||||
LOG("show keyboard");
|
||||
redraw = true;
|
||||
#ifdef __IOS__
|
||||
[ios_view registerForKeyboardNotifications];
|
||||
[ios_view becomeFirstResponder];
|
||||
#elif __ANDROID__
|
||||
displayKeyboard(and_app, true);
|
||||
@@ -34,11 +35,25 @@ void App::hideKeyboard()
|
||||
redraw = true;
|
||||
#ifdef __IOS__
|
||||
[ios_view resignFirstResponder];
|
||||
[ios_view unregisterForKeyboardNotifications];
|
||||
#elif __ANDROID__
|
||||
displayKeyboard(and_app, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void App::pick_image(std::function<void(std::string path)> callback)
|
||||
{
|
||||
redraw = true;
|
||||
#ifdef __IOS__
|
||||
[ios_view pick_photo:callback];
|
||||
#elif __OSX__
|
||||
std::string path = [osx_view pick_file];
|
||||
callback(path);
|
||||
#elif __ANDROID__
|
||||
//displayKeyboard(and_app, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool App::mouse_down(int button, float x, float y, float pressure, kEventSource source)
|
||||
{
|
||||
redraw = true;
|
||||
|
||||
@@ -350,6 +350,13 @@ void App::init_menu_file()
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
};
|
||||
popup->find<NodeButtonCustom>("file-import")->on_click = [this](Node*) {
|
||||
App::I.pick_image([](std::string path){
|
||||
Canvas::I->import_equirectangular(path);
|
||||
});
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
};
|
||||
popup->find<NodeButtonCustom>("file-open")->on_click = [this](Node*) {
|
||||
dialog_open();
|
||||
popup->mouse_release();
|
||||
|
||||
@@ -800,6 +800,45 @@ void ui::Canvas::clear_context()
|
||||
}
|
||||
};
|
||||
|
||||
void ui::Canvas::import_equirectangular(std::string file_path)
|
||||
{
|
||||
std::thread t(&ui::Canvas::import_equirectangular_thread, this, file_path);
|
||||
t.detach();
|
||||
}
|
||||
|
||||
void ui::Canvas::import_equirectangular_thread(std::string file_path)
|
||||
{
|
||||
Texture2D tex;
|
||||
gl_state gl;
|
||||
App::I.async_start();
|
||||
gl.save();
|
||||
snap_history({0,1,2,3,4,5});
|
||||
tex.load_file(file_path);
|
||||
ui::Sphere sphere;
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
sphere.create<64, 64>(2.f);
|
||||
draw_objects([&](const glm::mat4& camera, const glm::mat4& proj){
|
||||
m_sampler.bind(0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
tex.bind();
|
||||
ShaderManager::use(ui::kShader::Texture);
|
||||
ShaderManager::u_int(kShaderUniform::Tex, 0);
|
||||
ShaderManager::u_mat4(kShaderUniform::MVP, proj * camera *
|
||||
glm::eulerAngleY(glm::radians(-90.f)) * glm::scale(glm::vec3(1,-1,1)));
|
||||
sphere.draw_fill();
|
||||
tex.unbind();
|
||||
m_sampler.unbind();
|
||||
});
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
m_dirty_box[i] = glm::vec4(0, 0, m_width, m_height);
|
||||
m_dirty_face[i] = true;
|
||||
}
|
||||
App::I.async_update();
|
||||
gl.restore();
|
||||
App::I.async_end();
|
||||
}
|
||||
|
||||
void ui::Canvas::export_equirectangular(std::string file_path)
|
||||
{
|
||||
std::thread t(&ui::Canvas::export_equirectangular_thread, this, file_path);
|
||||
|
||||
@@ -131,6 +131,8 @@ public:
|
||||
void snapshot_restore();
|
||||
void snap_history(const std::vector<int>& planes);
|
||||
void clear_context();
|
||||
void import_equirectangular(std::string file_path);
|
||||
void import_equirectangular_thread(std::string file_path);
|
||||
void export_equirectangular(std::string file_path);
|
||||
void export_equirectangular_thread(std::string file_path);
|
||||
void export_anim(std::string data_path);
|
||||
|
||||
@@ -23,6 +23,15 @@ bool Image::load(std::string filename)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Image::load_file(std::string filename)
|
||||
{
|
||||
stbi_set_flip_vertically_on_load(false);
|
||||
uint8_t* buffer = stbi_load(filename.c_str(), &width, &height, nullptr, 4);
|
||||
comp = 4;
|
||||
m_data = std::unique_ptr<uint8_t[]>(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Image::flip()
|
||||
{
|
||||
auto flipped = std::make_unique<uint8_t[]>(width*height*4);
|
||||
|
||||
@@ -10,6 +10,7 @@ public:
|
||||
int height = 0;
|
||||
int comp = 4;
|
||||
bool load(std::string filename);
|
||||
bool load_file(std::string filename);
|
||||
const uint8_t* data() const { return m_data.get(); }
|
||||
int size() const { return width * height * comp; }
|
||||
void create(int w, int h)
|
||||
|
||||
@@ -125,7 +125,7 @@ void NodeCanvas::draw()
|
||||
ui::ShaderManager::u_int(kShaderUniform::TexStroke, 1);
|
||||
ui::ShaderManager::u_int(kShaderUniform::TexMask, 2);
|
||||
ui::ShaderManager::u_float(kShaderUniform::Alpha, m_canvas->m_current_stroke->m_brush.m_tip_opacity);
|
||||
ui::ShaderManager::u_int(kShaderUniform::Lock, m_canvas->m_layers[m_canvas->m_current_layer_idx].m_alpha_locked);
|
||||
ui::ShaderManager::u_int(kShaderUniform::Lock, m_canvas->m_layers[layer_index].m_alpha_locked);
|
||||
ui::ShaderManager::u_int(kShaderUniform::Mask, m_canvas->m_smask_active);
|
||||
ui::ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
@@ -151,7 +151,7 @@ void NodeCanvas::draw()
|
||||
ui::ShaderManager::u_int(kShaderUniform::TexMask, 2);
|
||||
//ui::ShaderManager::u_int(kShaderUniform::TexStencil, 3);
|
||||
ui::ShaderManager::u_float(kShaderUniform::Alpha, m_canvas->m_current_stroke->m_brush.m_tip_opacity);
|
||||
ui::ShaderManager::u_int(kShaderUniform::Lock, m_canvas->m_layers[m_canvas->m_current_layer_idx].m_alpha_locked);
|
||||
ui::ShaderManager::u_int(kShaderUniform::Lock, m_canvas->m_layers[layer_index].m_alpha_locked);
|
||||
ui::ShaderManager::u_int(kShaderUniform::Mask, m_canvas->m_smask_active);
|
||||
ui::ShaderManager::u_int(kShaderUniform::BlendMode, m_canvas->m_current_stroke->m_brush.m_blend_mode);
|
||||
ui::ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z);
|
||||
|
||||
@@ -94,6 +94,6 @@ void NodeColorQuad::draw()
|
||||
using namespace ui;
|
||||
ui::ShaderManager::use(kShader::ColorQuad);
|
||||
ui::ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp);
|
||||
ui::ShaderManager::u_vec4(kShaderUniform::Col, m_color);
|
||||
ui::ShaderManager::u_vec4(kShaderUniform::Col, glm::vec4(convert_rgb2hsv(glm::vec3(m_color)), 1));
|
||||
m_plane.draw_fill();
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
#include <future>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
@@ -456,6 +456,37 @@ void Slice9::create_impl(float w, float h, float r, float tr, GLushort *idx, Sha
|
||||
*idx++ = 12; // D
|
||||
*idx++ = 0; // A
|
||||
}
|
||||
void Sphere::create_impl(int rings, int sectors, float radius, GLushort *idx, Shape::vertex_t *vertices)
|
||||
{
|
||||
count[0] = rings * sectors * 6;
|
||||
count[1] = 0;
|
||||
ioff[0] = (GLvoid*)0;
|
||||
ioff[1] = (GLvoid*)0;
|
||||
|
||||
float const R = 1.f / (float)(rings-1);
|
||||
float const S = 1.f / (float)(sectors-1);
|
||||
int r, s;
|
||||
|
||||
auto v = vertices;
|
||||
for(r = 0; r < rings; r++) for(s = 0; s < sectors; s++) {
|
||||
float const y = (float)sin( -M_PI_2 + M_PI * r * R );
|
||||
float const x = (float)cos(2*M_PI * s * S) * (float)sin( M_PI * r * R );
|
||||
float const z = (float)sin(2*M_PI * s * S) * (float)sin( M_PI * r * R );
|
||||
|
||||
*v++ = { glm::vec4(x, y, z, 1) * radius, glm::vec2(s*S, r*R) };
|
||||
}
|
||||
|
||||
auto i = idx;
|
||||
for(r = 0; r < rings-1; r++) for(s = 0; s < sectors-1; s++) {
|
||||
*i++ = r * sectors + s;
|
||||
*i++ = r * sectors + (s+1);
|
||||
*i++ = (r+1) * sectors + (s+1);
|
||||
|
||||
*i++ = r * sectors + s;
|
||||
*i++ = (r+1) * sectors + (s+1);
|
||||
*i++ = (r+1) * sectors + s;
|
||||
}
|
||||
}
|
||||
void ui::LineSegment::update_vertices(const glm::vec4 data[2])
|
||||
{
|
||||
static vertex_t vertices[2];
|
||||
|
||||
@@ -194,4 +194,18 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class Sphere : public Shape
|
||||
{
|
||||
void create_impl(int rings, int sectors, float radius, GLushort* idx, vertex_t* vertices);
|
||||
public:
|
||||
template<int rings, int sectors>
|
||||
bool create(float radius)
|
||||
{
|
||||
static GLushort idx[rings * sectors * 6];
|
||||
static vertex_t vertices[rings * sectors];
|
||||
create_impl(rings, sectors, radius, idx, vertices);
|
||||
return create_buffers(idx, vertices, sizeof(idx), sizeof(vertices));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -82,6 +82,16 @@ bool Texture2D::load(std::string filename)
|
||||
return false;
|
||||
return create(img);
|
||||
}
|
||||
|
||||
bool Texture2D::load_file(std::string filename)
|
||||
{
|
||||
LOG("load texture %s", filename.c_str());
|
||||
ui::Image img;
|
||||
if (!img.load_file(filename))
|
||||
return false;
|
||||
return create(img);
|
||||
}
|
||||
|
||||
void Texture2D::update(const uint8_t* data)
|
||||
{
|
||||
bind();
|
||||
|
||||
@@ -13,6 +13,7 @@ public:
|
||||
bool create(const ui::Image& img);
|
||||
void assign(GLuint tex, int w = -1, int h = -1, GLuint internal_format = GL_RGBA8, GLuint format = GL_RGBA);
|
||||
bool load(std::string filename);
|
||||
bool load_file(std::string filename);
|
||||
void destroy() { if (m_tex) LOG("TEX destroy %d", m_tex); glDeleteTextures(1, &m_tex); }
|
||||
void bind() const { glBindTexture(GL_TEXTURE_2D, m_tex); }
|
||||
void unbind() const { glBindTexture(GL_TEXTURE_2D, 0); }
|
||||
|
||||
Reference in New Issue
Block a user