From 6dfbf8fd561187ef23eacf9aaae606c0fb42381f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=A6=8C=EA=A6=AB=EA=A6=B6=EA=A6=8F=EA=A7=80=EA=A6=A6?= =?UTF-8?q?=EA=A6=BF=EA=A6=A7=EA=A6=AE=EA=A6=91=EA=A6=A9=EA=A6=AD=EA=A7=80?= Date: Thu, 3 Jun 2021 20:28:59 +0800 Subject: Initial working commit --- .gitignore | 25 ++++ COPYING | 20 ++++ Makefile.am | 3 + README.md | 19 ++++ aarch64-linux-android-configure | 9 ++ arm-apple-darwin11-configure | 6 + configure.ac | 10 ++ converter.c | 67 +++++++++++ i686-w64-mingw32-configure | 6 + product.frag | 7 ++ product.vert | 9 ++ productviewer.h | 18 +++ viewer.c | 233 ++++++++++++++++++++++++++++++++++++++ wasm-unknown-emscripten-configure | 5 + x86_64-pc-linux-gnu-configure | 5 + 15 files changed, 442 insertions(+) create mode 100644 .gitignore create mode 100644 COPYING create mode 100644 Makefile.am create mode 100644 README.md create mode 100755 aarch64-linux-android-configure create mode 100755 arm-apple-darwin11-configure create mode 100644 configure.ac create mode 100644 converter.c create mode 100755 i686-w64-mingw32-configure create mode 100644 product.frag create mode 100644 product.vert create mode 100644 productviewer.h create mode 100644 viewer.c create mode 100755 wasm-unknown-emscripten-configure create mode 100755 x86_64-pc-linux-gnu-configure diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1c157ce --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +*~ +*.la +*.lo +*.o +*.swp +*.wasm +.deps +.libs +Makefile +Makefile.am.user* +Makefile.in +aclocal.m4 +ar-lib +autom4te.cache +autoscan-2.69.log +compile +config.* +configure +configure.scan +depcomp +install-sh +libtool +ltmain.sh +m4 +missing diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..086fa3c --- /dev/null +++ b/COPYING @@ -0,0 +1,20 @@ +Copyright (c) 2019 Erik Prabowo Kamal + +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. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..e11069c --- /dev/null +++ b/Makefile.am @@ -0,0 +1,3 @@ +lib_LTLIBRARIES = libproductviewer.la +libproductviewer_la_SOURCES = viewer.c converter.c +include_HEADERS = productviewer.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..8ef924e --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# Portable 3D viewer using OpenGL ES 2 + +# Building + +## Getting and preparing for configuration + +```sh +$ git clone git://darapsa.org/libproductviewer.git +$ cd libproductviewer +$ autoreconf --install +``` + +## Configuring for various target hosts, compiling, linking, and installing + +```sh +$ ./configure (or use the platform specific wrappers, and adjust as necessary) +$ make # -jN (with N an integer number of parallel tasks you allow your computer to run for compiling this) +$ sudo make install +``` diff --git a/aarch64-linux-android-configure b/aarch64-linux-android-configure new file mode 100755 index 0000000..8690ece --- /dev/null +++ b/aarch64-linux-android-configure @@ -0,0 +1,9 @@ +#!/bin/sh +export TOOLCHAIN=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64 +export TARGET=aarch64-linux-android +export API=21 +export CC=$TOOLCHAIN/bin/$TARGET$API-clang +export PREFIX=$TOOLCHAIN/sysroot/usr +export CPPFLAGS="$CPPFLAGS -DDEBUG" +export CFLAGS="$CFLAGS -g" +./configure --host=$TARGET --prefix=$PREFIX --libdir=$PREFIX/lib/$TARGET/$API --disable-static diff --git a/arm-apple-darwin11-configure b/arm-apple-darwin11-configure new file mode 100755 index 0000000..c637d5d --- /dev/null +++ b/arm-apple-darwin11-configure @@ -0,0 +1,6 @@ +#!/bin/sh +export TARGET=arm-apple-darwin11 +export CC=ios-clang +export PREFIX=/opt/iPhoneOS8.1.sdk +export CFLAGS="$CFLAGS -g0 -O2 -I$PREFIX/System/Library/Frameworks/OpenGLES.framework/Headers" +./configure --host=$TARGET --prefix=$PREFIX/usr --disable-shared diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..d3b25b2 --- /dev/null +++ b/configure.ac @@ -0,0 +1,10 @@ +AC_INIT([productviewer], [0.0], [pt@darapsa.co.id]) +AM_INIT_AUTOMAKE([-Wall -Werror foreign]) +AC_PROG_CC +AC_CONFIG_FILES([Makefile]) +AC_TYPE_SIZE_T +AC_CHECK_FUNCS([strrchr]) +AC_CHECK_FUNCS([memset]) +AM_PROG_AR +LT_INIT +AC_OUTPUT diff --git a/converter.c b/converter.c new file mode 100644 index 0000000..ded4f5d --- /dev/null +++ b/converter.c @@ -0,0 +1,67 @@ +#define TINYOBJ_LOADER_C_IMPLEMENTATION +#include + +static tinyobj_attrib_t attrib; +static tinyobj_material_t *materials = NULL; +static size_t num_materials; + +static void read_file(void *ctx, const char *filename, const int is_mtl, const char *obj_filename, + char **data, size_t *len) +{ + char *last_delim = strrchr(obj_filename, +#ifdef _WIN32 + '\\' +#else + '/' +#endif + ); + char path[strlen(obj_filename) - (last_delim ? strlen(last_delim) + 1 : 0) + 1]; + if (is_mtl && last_delim) { + last_delim[0] = '\0'; + sprintf(path, "%s/%s", obj_filename, filename); + } else + strcpy(path, filename); + ((void (*)(const char *, char **, size_t *))ctx)(path, data, len); +} + +void pv_parse(unsigned int *num_triangles, size_t *tex_name_len, const char *model, void *file_reader) +{ + tinyobj_shape_t *shapes = NULL; + size_t num_shapes; + tinyobj_parse_obj(&attrib, &shapes, &num_shapes, &materials, &num_materials, model, read_file, file_reader, + TINYOBJ_FLAG_TRIANGULATE); + tinyobj_shapes_free(shapes, num_shapes); + *num_triangles = attrib.num_face_num_verts; + *tex_name_len = strlen(materials[0].diffuse_texname); +} + +void pv_convert(float *data, char *texture) +{ + static const size_t stride = 5; + for (unsigned int i = 0; i < attrib.num_face_num_verts; i++) { + tinyobj_vertex_index_t idx0 = attrib.faces[i * 3]; + tinyobj_vertex_index_t idx1 = attrib.faces[i * 3 + 1]; + tinyobj_vertex_index_t idx2 = attrib.faces[i * 3 + 2]; + float v[3][3]; + for (size_t j = 0; j < 3; j++) { + v[0][j] = attrib.vertices[idx0.v_idx * 3 + j]; + v[1][j] = attrib.vertices[idx1.v_idx * 3 + j]; + v[2][j] = attrib.vertices[idx2.v_idx * 3 + j]; + } + float t[][2] = { + attrib.texcoords[idx0.vt_idx * 2], 1.0f - attrib.texcoords[idx0.vt_idx * 2 + 1], + attrib.texcoords[idx1.vt_idx * 2], 1.0f - attrib.texcoords[idx1.vt_idx * 2 + 1], + attrib.texcoords[idx2.vt_idx * 2], 1.0f - attrib.texcoords[idx2.vt_idx * 2 + 1] + }; + for (size_t j = 0; j < 3; j++) { + data[(i * 3 + j) * stride + 0] = v[j][0]; + data[(i * 3 + j) * stride + 1] = v[j][1]; + data[(i * 3 + j) * stride + 2] = v[j][2]; + data[(i * 3 + j) * stride + 3] = t[j][0]; + data[(i * 3 + j) * stride + 4] = t[j][1]; + } + } + strcpy(texture, materials[0].diffuse_texname); + tinyobj_attrib_free(&attrib); + tinyobj_materials_free(materials, num_materials); +} diff --git a/i686-w64-mingw32-configure b/i686-w64-mingw32-configure new file mode 100755 index 0000000..39b2b15 --- /dev/null +++ b/i686-w64-mingw32-configure @@ -0,0 +1,6 @@ +#!/bin/sh +export TARGET=i686-w64-mingw32 +export CC=$TARGET-gcc +export CPPFLAGS="$CPPFLAGS -DDEBUG" +export CFLAGS="$CFLAGS -g" +./configure --host=$TARGET --prefix=/usr/$TARGET/usr --disable-shared diff --git a/product.frag b/product.frag new file mode 100644 index 0000000..6bbb24d --- /dev/null +++ b/product.frag @@ -0,0 +1,7 @@ +precision mediump float; +varying vec2 v_texcoord; +uniform sampler2D s_texture; +void main() +{ + gl_FragColor = texture2D(s_texture, v_texcoord); +} diff --git a/product.vert b/product.vert new file mode 100644 index 0000000..b040d01 --- /dev/null +++ b/product.vert @@ -0,0 +1,9 @@ +uniform mat4 u_matrix; +attribute vec4 a_position; +attribute vec2 a_texcoord; +varying vec2 v_texcoord; +void main() +{ + gl_Position = u_matrix * a_position; + v_texcoord = a_texcoord; +} diff --git a/productviewer.h b/productviewer.h new file mode 100644 index 0000000..191cfe5 --- /dev/null +++ b/productviewer.h @@ -0,0 +1,18 @@ +#ifndef PRODUCTVIEWER_H +#define PRODUCTVIEWER_H + +#ifdef __cplusplus +extern "C" { +#endif + +void pv_init(const char *model, void (*file_reader)(const char *path, char **data, size_t *len), + const char *color, unsigned int width, unsigned int height); +void pv_draw(unsigned int width, unsigned int height); +void pv_rotate(float angle); +void pv_quit(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/viewer.c b/viewer.c new file mode 100644 index 0000000..cc27e7f --- /dev/null +++ b/viewer.c @@ -0,0 +1,233 @@ +#ifdef __APPLE__ +#include +#else +#include +#endif +#define STB_IMAGE_IMPLEMENTATION +#include +#if defined __ANDROID__ && defined DEBUG +#include +#endif + +static struct { + void (*file_reader)(const char *, char **, size_t *); + GLfloat matrix[4][4]; + GLfloat angle; + GLsizei num_triangles, num_textures; + GLuint program, buffers[1], textures[]; +} model = { + .angle = 0.0f, + .num_textures = 0 +}; +static const GLfloat pi = 3.1415926535897932384626433832795f; + +void pv_parse(GLsizei *num_triangles, size_t *tex_name_len, const char *model, void *file_reader); +void pv_convert(float *data, char *texture); + +static GLuint compile(GLenum type, const char *shader) +{ + GLchar *data; + size_t len; + model.file_reader(shader, &data, &len); + GLuint id = glCreateShader(type); + glShaderSource(id, 1, &(const GLchar *){data}, NULL); + glCompileShader(id); + GLint compiled; + glGetShaderiv(id, GL_COMPILE_STATUS, &compiled); + if (!compiled) { +#ifdef DEBUG + GLint len = 0; + glGetShaderiv(id, GL_INFO_LOG_LENGTH, &len); + if (len > 1) { + char log[len + 1]; + glGetShaderInfoLog(id, len, NULL, log); +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "libproductviewer", "%s", log); +#else + printf("%s\n", log); +#endif + } +#endif + glDeleteShader(id); + return 0; + } + return id; +} + +void pv_texturize(const char *texture) +{ + GLsizei w, h; + int comp; + unsigned char *image = stbi_load(texture, &w, &h, &comp, STBI_default); + GLint format = GL_RGBA; + if (comp == 3) + format = GL_RGB; + glTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0, format, GL_UNSIGNED_BYTE, image); + stbi_image_free(image); +} + +void pv_init(const char *obj, void (*file_reader)(const char *path, char **data, size_t *len), + const char *color, unsigned int width, unsigned int height) +{ + size_t tex_name_len; + model.file_reader = file_reader; + pv_parse(&model.num_triangles, &tex_name_len, obj, file_reader); + GLfloat data[sizeof(GLfloat) * model.num_triangles * 5]; + char texture[tex_name_len + 1]; + pv_convert(data, texture); + GLfloat rgba[4] = { [3] = 1.0f }; + for (size_t i = 0; i < strlen(color) / 2; i++) { + GLuint value; + sscanf((char[]){ color[i * 2], color[i * 2 + 1], '\0' }, "%x", &value); + rgba[i] = (GLfloat)value / 255.0f; + } + glClearColor(rgba[0], rgba[1], rgba[2], rgba[3]); + glViewport(0, 0, width, height); + glEnable(GL_CULL_FACE); + model.program = glCreateProgram(); + GLuint vert_id = compile(GL_VERTEX_SHADER, "product.vert"); + glAttachShader(model.program, vert_id); + glDeleteShader(vert_id); + GLuint frag_id = compile(GL_FRAGMENT_SHADER, "product.frag"); + glAttachShader(model.program, frag_id); + glDeleteShader(frag_id); + glLinkProgram(model.program); + glUseProgram(model.program); + static const GLsizei stride = sizeof(GLfloat) * 5; + GLuint data_id; + glGenBuffers(1, &data_id); + glBindBuffer(GL_ARRAY_BUFFER, data_id); + model.buffers[0] = data_id; + glBufferData(GL_ARRAY_BUFFER, stride * model.num_triangles * 3, data, GL_STATIC_DRAW); + GLint position = glGetAttribLocation(model.program, "a_position"); + glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, stride, 0); + glEnableVertexAttribArray(position); + GLint texcoord = glGetAttribLocation(model.program, "a_texcoord"); + glVertexAttribPointer(texcoord, 2, GL_FLOAT, GL_FALSE, stride, (const void *)(sizeof(GLfloat) * 3)); + glEnableVertexAttribArray(texcoord); + glActiveTexture(GL_TEXTURE0); + GLuint tex_id; + glGenTextures(1, &tex_id); + glBindTexture(GL_TEXTURE_2D, tex_id); + model.textures[model.num_textures++] = tex_id; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glUniform1i(glGetUniformLocation(model.program, "s_texture"), 0); + pv_texturize(texture); +} + +static void identity(GLfloat matrix[][4]) +{ + memset(matrix, 0x0, sizeof(GLfloat[4][4])); + for (int i = 0; i < 4; i++) + matrix[i][i] = 1.0f; +} + +static void multiply(GLfloat matrix[][4], GLfloat a[][4], GLfloat b[][4]) +{ + GLfloat tmp[4][4]; + for (int i = 0; i < 4; i++) { + tmp[i][0] + = a[i][0] * b[0][0] + + a[i][1] * b[1][0] + + a[i][2] * b[2][0] + + a[i][3] * b[3][0]; + tmp[i][1] + = a[i][0] * b[0][1] + + a[i][1] * b[1][1] + + a[i][2] * b[2][1] + + a[i][3] * b[3][1]; + tmp[i][2] + = a[i][0] * b[0][2] + + a[i][1] * b[1][2] + + a[i][2] * b[2][2] + + a[i][3] * b[3][2]; + tmp[i][3] + = a[i][0] * b[0][3] + + a[i][1] * b[1][3] + + a[i][2] * b[2][3] + + a[i][3] * b[3][3]; + } + memcpy(matrix, tmp, sizeof(GLfloat[4][4])); +} + +static void translate(GLfloat matrix[][4], GLfloat x, GLfloat y, GLfloat z) +{ + matrix[3][0] += (matrix[0][0] * x + matrix[1][0] * y + matrix[2][0] * z); + matrix[3][1] += (matrix[0][1] * x + matrix[1][1] * y + matrix[2][1] * z); + matrix[3][2] += (matrix[0][2] * x + matrix[1][2] * y + matrix[2][2] * z); + matrix[3][3] += (matrix[0][3] * x + matrix[1][3] * y + matrix[2][3] * z); +} + +static void rotate(GLfloat matrix[][4], GLfloat angle, GLfloat x, GLfloat y, GLfloat z) +{ + angle *= pi / 180.0f; + GLfloat sin_angle = sinf(angle); + GLfloat cos_angle = cosf(angle); + GLfloat mag = sqrtf(x * x + y * y + z * z); + if (mag > 0.0f) { + x /= mag; + y /= mag; + z /= mag; + GLfloat one_minus_cos = 1.0f - cos_angle; + multiply(matrix, (GLfloat[][4]){ + (one_minus_cos * x * x) + cos_angle, + (one_minus_cos * x * y) - z * sin_angle, + (one_minus_cos * z * x) + y * sin_angle, + 0.0f, + (one_minus_cos * x * y) + z * sin_angle, + (one_minus_cos * y * y) + cos_angle, + (one_minus_cos * y * z) - x * sin_angle, + 0.0f, + (one_minus_cos * z * x) - y * sin_angle, + (one_minus_cos * y * z) + x * sin_angle, + (one_minus_cos * z * z) + cos_angle, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f + }, + matrix); + } +} + +void pv_draw(unsigned int width, unsigned int height) +{ + GLfloat perspective[4][4]; + identity(perspective); + GLfloat near_z = 1.0f; + GLfloat far_z = 20.0f; + GLfloat delta_y = tanf(pi / 6.0f) * 2; + GLfloat delta_z = far_z - near_z; + multiply(perspective, (GLfloat[][4]){ + 2.0f * near_z / delta_y / (GLfloat)width * (GLfloat)height, 0.0f, 0.0f, 0.0f, + 0.0f, 2.0f * near_z / delta_y, 0.0f, 0.0f, + 0.0f, 0.0f, -(near_z + far_z) / delta_z, -1.0f, + 0.0f, 0.0f, -2.0f * near_z * far_z / delta_z, 0.0f + }, + perspective); + GLfloat model_view[4][4]; + identity(model_view); + translate(model_view, 0.0, 0.0, -2.7); + rotate(model_view, -15.0, 1.0, 0.0, 0.0); + rotate(model_view, model.angle, 0.0, 1.0, 0.0); + multiply(model.matrix, model_view, perspective); + glUniformMatrix4fv(glGetUniformLocation(model.program, "u_matrix"), 1, GL_FALSE, &model.matrix[0][0]); + glClear(GL_COLOR_BUFFER_BIT); + glDrawArrays(GL_TRIANGLES, 0, model.num_triangles * 3); +} + +void pv_rotate(GLfloat angle) +{ + model.angle += angle; + if (model.angle >= 360.0f) + model.angle -= 360.0f; +} + +void pv_quit() +{ + glDeleteTextures(model.num_textures, model.textures); + glDeleteBuffers(1, model.buffers); + glDeleteProgram(model.program); +} diff --git a/wasm-unknown-emscripten-configure b/wasm-unknown-emscripten-configure new file mode 100755 index 0000000..881c418 --- /dev/null +++ b/wasm-unknown-emscripten-configure @@ -0,0 +1,5 @@ +#!/bin/sh +export PREFIX=$EMSDK/upstream/emscripten/cache/wasm +export CPPFLAGS="$CPPFLAGS -DDEBUG" +export CFLAGS="$CFLAGS -g" +emconfigure ./configure --prefix=$PREFIX --libdir=$PREFIX --disable-shared diff --git a/x86_64-pc-linux-gnu-configure b/x86_64-pc-linux-gnu-configure new file mode 100755 index 0000000..d5f8898 --- /dev/null +++ b/x86_64-pc-linux-gnu-configure @@ -0,0 +1,5 @@ +#!/bin/sh +export PREFIX=/usr/local +export CPPFLAGS="$CPPFLAGS -DDEBUG" +export CFLAGS="$CFLAGS -g" +./configure --prefix=$PREFIX --libdir=$PREFIX/lib64 --disable-static -- cgit v1.2.3