diff --git a/src/core.h b/src/core.h index c5d3920..e63db39 100644 --- a/src/core.h +++ b/src/core.h @@ -32,6 +32,12 @@ #define _GAPI_GLES 1 #define DYNGEOM_NO_VBO +#elif __CLOVER__ + #define _OS_CLOVER 1 + #define _GAPI_GL 1 + #define _GAPI_GLES 1 + + //#define DYNGEOM_NO_VBO #elif __linux__ #define _OS_LINUX 1 #define _GAPI_GL 1 @@ -704,7 +710,7 @@ namespace Core { settings.controls[0].keys[ cInventory ].key = ikTab; #endif - #ifdef _OS_RPI + #if defined(_OS_RPI) || defined(_OS_CLOVER) settings.detail.setShadows (Core::Settings::LOW); settings.detail.setLighting (Core::Settings::MEDIUM); #endif diff --git a/src/gapi_gl.h b/src/gapi_gl.h index f3c6a6e..2c47a5c 100644 --- a/src/gapi_gl.h +++ b/src/gapi_gl.h @@ -38,7 +38,7 @@ #define glProgramBinary glProgramBinaryOES #define GL_PROGRAM_BINARY_LENGTH GL_PROGRAM_BINARY_LENGTH_OES -#elif _OS_RPI +#elif defined(_OS_RPI) || defined(_OS_CLOVER) #include #include #include @@ -133,7 +133,7 @@ #define glProgramBinary(...) #endif -#if defined(_OS_WIN) || (defined(_OS_LINUX) && !defined(_OS_RPI)) || defined(_OS_ANDROID) +#if defined(_OS_WIN) || defined(_OS_LINUX) || defined(_OS_ANDROID) #ifdef _OS_ANDROID #define GetProc(x) dlsym(libGL, x); @@ -143,7 +143,7 @@ return (void*)wglGetProcAddress(name); #elif _OS_LINUX return (void*)glXGetProcAddress((GLubyte*)name); - #elif _OS_RPI + #else // EGL return (void*)eglGetProcAddress(name); #endif } @@ -228,7 +228,7 @@ PFNGLPROGRAMBINARYPROC glProgramBinary; #endif -#if defined(_GAPI_GLES) && !defined(_OS_RPI) +#if defined(_GAPI_GLES) && !defined(_OS_RPI) && !defined(_OS_CLOVER) PFNGLDISCARDFRAMEBUFFEREXTPROC glDiscardFramebufferEXT; #endif @@ -369,11 +369,16 @@ namespace GAPI { } sprintf(defines, "%s#define PASS_%s\n", defines, passNames[pass]); - #ifdef _OS_RPI + #if defined(_OS_RPI) || defined(_OS_CLOVER) strcat(defines, "#define OPT_VLIGHTPROJ\n"); + strcat(defines, "#define OPT_VLIGHTVEC\n"); strcat(defines, "#define OPT_SHADOW_ONETAP\n"); #endif + #ifndef _OS_CLOVER + strcat(defines, "#define OPT_TRAPEZOID\n"); // TODO: only for non Mali-400? + #endif + char fileName[255]; // generate shader file path if (Core::support.shaderBinary) { @@ -1068,7 +1073,7 @@ namespace GAPI { if (wglSwapIntervalEXT) wglSwapIntervalEXT(enable ? 1 : 0); #elif _OS_LINUX if (glXSwapIntervalSGI) glXSwapIntervalSGI(enable ? 1 : 0); - #elif _OS_RPI + #elif defined(_OS_RPI) || defined(_OS_CLOVER) eglSwapInterval(display, enable ? 1 : 0); #endif } diff --git a/src/platform/clover/build.sh b/src/platform/clover/build.sh new file mode 100755 index 0000000..d7d54ea --- /dev/null +++ b/src/platform/clover/build.sh @@ -0,0 +1,3 @@ +set -e +clang++ -std=c++11 -Os -s -g -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections -Wl,--gc-sections -DNDEBUG -D__CLOVER__ main.cpp ../../libs/stb_vorbis/stb_vorbis.c ../../libs/minimp3/minimp3.cpp ../../libs/tinf/tinflate.c -I/opt/vc/include -I../../ -L/opt/vc/lib/ -lGLESv2 -lEGL -lm -lrt -lpthread -lasound -ludev -o../../../bin/OpenLara +strip ../../../bin/OpenLara --strip-all --remove-section=.comment --remove-section=.note diff --git a/src/platform/clover/main.cpp b/src/platform/clover/main.cpp new file mode 100644 index 0000000..8a9647b --- /dev/null +++ b/src/platform/clover/main.cpp @@ -0,0 +1,590 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "game.h" + +#define WND_TITLE "OpenLara" + +// timing +unsigned int startTime; + +int osGetTime() { + timeval t; + gettimeofday(&t, NULL); + return int((t.tv_sec - startTime) * 1000 + t.tv_usec / 1000); +} + +// sound +snd_pcm_uframes_t SND_FRAMES = 512; +snd_pcm_t *sndOut; +Sound::Frame *sndData; +pthread_t sndThread; + +void* sndFill(void *arg) { + while (sndOut) { + Sound::fill(sndData, SND_FRAMES); + + int count = SND_FRAMES; + while (count > 0) { + int frames = snd_pcm_writei(sndOut, &sndData[SND_FRAMES - count], count); + if (frames < 0) { + frames = snd_pcm_recover(sndOut, frames, 0); + if (frames == -EAGAIN) { + LOG("snd_pcm_writei try again\n"); + sleep(1); + continue; + } + if (frames < 0) { + LOG("snd_pcm_writei failed: %s\n", snd_strerror(frames)); + sndOut = NULL; + return NULL; + } + } + count -= frames; + } + + snd_pcm_prepare(sndOut); + } + return NULL; +} + +bool sndInit() { + unsigned int freq = 44100; + + int err; + + // In the perfect world ReedPlayer-Clover process + // will release ALSA devicbefore app running, but... + for (int i = 0; i < 20; i++) { // 20 * 0.1 = 2 secs + sndOut = NULL; + if ((err = snd_pcm_open(&sndOut, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + LOG("sound: try to snd_pcm_open #%d...\n", i); + usleep(100000); // wait for 100 ms + continue; + } + break; + } + + // I've bad news for you + if (!sndOut) { + LOG("! sound: snd_pcm_open %s\n", snd_strerror(err)); + return false; + } + + snd_pcm_hw_params_t *params; + + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(sndOut, params); + snd_pcm_hw_params_set_access(sndOut, params, SND_PCM_ACCESS_RW_INTERLEAVED); + + snd_pcm_hw_params_set_channels(sndOut, params, 2); + snd_pcm_hw_params_set_format(sndOut, params, SND_PCM_FORMAT_S16_LE); + snd_pcm_hw_params_set_rate_near(sndOut, params, &freq, NULL); + + snd_pcm_hw_params_set_periods(sndOut, params, 4, 0); + snd_pcm_hw_params_set_period_size_near(sndOut, params, &SND_FRAMES, NULL); + snd_pcm_hw_params_get_period_size(params, &SND_FRAMES, 0); + + snd_pcm_hw_params(sndOut, params); + snd_pcm_prepare(sndOut); + + sndData = new Sound::Frame[SND_FRAMES]; + memset(sndData, 0, SND_FRAMES * sizeof(Sound::Frame)); + if ((err = snd_pcm_writei(sndOut, sndData, SND_FRAMES)) < 0) { + LOG("! sound: write %s\n", snd_strerror(err)); + sndOut = NULL; + } + + snd_pcm_start(sndOut); + pthread_create(&sndThread, NULL, sndFill, NULL); + + return true; +} + +void sndFree() { + pthread_cancel(sndThread); + snd_pcm_drop(sndOut); + snd_pcm_drain(sndOut); + snd_pcm_close(sndOut); + delete[] sndData; +} + +// Window +struct FrameBuffer { + unsigned short width; + unsigned short height; +} fb; + +EGLDisplay display; +EGLSurface surface; +EGLContext context; + +bool eglInit() { + LOG("EGL init context...\n"); + + fb_var_screeninfo vinfo; + int fd = open("/dev/fb0", O_RDWR); + if (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo) < 0) { + LOG("! can't get framebuffer size\n"); + return false; + } + close(fd); + + fb.width = vinfo.xres; + fb.height = vinfo.yres; + + static const EGLint eglAttr[] = { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_BLUE_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_RED_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_NONE + }; + + static const EGLint ctxAttr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) { + LOG("eglGetDisplay = EGL_NO_DISPLAY\n"); + return false; + } + + if (eglInitialize(display, NULL, NULL) == EGL_FALSE) { + LOG("eglInitialize = EGL_FALSE\n"); + return false; + } + + EGLConfig config; + EGLint configCount; + + if (eglChooseConfig(display, eglAttr, &config, 1, &configCount) == EGL_FALSE) { + LOG("eglChooseConfig = EGL_FALSE\n"); + return false; + } + + surface = eglCreateWindowSurface(display, config, &fb, NULL); + if (surface == EGL_NO_SURFACE) { + LOG("eglCreateWindowSurface = EGL_NO_SURFACE\n"); + return false; + } + + context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctxAttr); + if (context == EGL_NO_CONTEXT) { + LOG("eglCreateContext = EGL_NO_CONTEXT\n"); + return false; + } + + if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) { + LOG("eglMakeCurrent = EGL_FALSE\n"); + return false; + } + + return true; +} + +void eglFree() { + LOG("EGL release context\n"); + eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroySurface(display, surface); + eglDestroyContext(display, context); + eglTerminate(display); +} + +// Input +#define MAX_INPUT_DEVICES 16 +struct InputDevice { + int fd; + int joyIndex; +} inputDevices[MAX_INPUT_DEVICES]; + +udev *udevObj; +udev_monitor *udevMon; +int udevMon_fd; + +vec2 joyL, joyR; + +bool osJoyReady(int index) { + return index == 0; // TODO +} + +void osJoyVibrate(int index, float L, float R) { + // TODO +} + +InputKey codeToInputKey(int code) { + switch (code) { + // keyboard + case KEY_LEFT : return ikLeft; + case KEY_RIGHT : return ikRight; + case KEY_UP : return ikUp; + case KEY_DOWN : return ikDown; + case KEY_SPACE : return ikSpace; + case KEY_TAB : return ikTab; + case KEY_ENTER : return ikEnter; + case KEY_ESC : return ikEscape; + case KEY_LEFTSHIFT : + case KEY_RIGHTSHIFT : return ikShift; + case KEY_LEFTCTRL : + case KEY_RIGHTCTRL : return ikCtrl; + case KEY_LEFTALT : + case KEY_RIGHTALT : return ikAlt; + case KEY_0 : return ik0; + case KEY_1 : return ik1; + case KEY_2 : return ik2; + case KEY_3 : return ik3; + case KEY_4 : return ik4; + case KEY_5 : return ik5; + case KEY_6 : return ik6; + case KEY_7 : return ik7; + case KEY_8 : return ik8; + case KEY_9 : return ik9; + case KEY_A : return ikA; + case KEY_B : return ikB; + case KEY_C : return ikC; + case KEY_D : return ikD; + case KEY_E : return ikE; + case KEY_F : return ikF; + case KEY_G : return ikG; + case KEY_H : return ikH; + case KEY_I : return ikI; + case KEY_J : return ikJ; + case KEY_K : return ikK; + case KEY_L : return ikL; + case KEY_M : return ikM; + case KEY_N : return ikN; + case KEY_O : return ikO; + case KEY_P : return ikP; + case KEY_Q : return ikQ; + case KEY_R : return ikR; + case KEY_S : return ikS; + case KEY_T : return ikT; + case KEY_U : return ikU; + case KEY_V : return ikV; + case KEY_W : return ikW; + case KEY_X : return ikX; + case KEY_Y : return ikY; + case KEY_Z : return ikZ; + case KEY_HOMEPAGE : return ikEscape; + // mouse + case BTN_LEFT : return ikMouseL; + case BTN_RIGHT : return ikMouseR; + case BTN_MIDDLE : return ikMouseM; + // system keys + case KEY_VOLUMEUP : + case BTN_MODE : + Core::quit(); + return ikNone; + } + return ikNone; +} + +JoyKey codeToJoyKey(int code) { + switch (code) { + // gamepad + case BTN_TRIGGER_HAPPY1 : return jkLeft; + case BTN_TRIGGER_HAPPY2 : return jkRight; + case BTN_TRIGGER_HAPPY3 : return jkUp; + case BTN_TRIGGER_HAPPY4 : return jkDown; + case BTN_A : return jkB; + case BTN_B : return jkA; + case BTN_X : return jkY; + case BTN_Y : return jkX; + case BTN_TL : return jkLB; + case BTN_TR : return jkRB; + case BTN_SELECT : return jkSelect; + case BTN_START : return jkStart; + case BTN_THUMBL : return jkL; + case BTN_THUMBR : return jkR; + case BTN_TL2 : return jkLT; + case BTN_TR2 : return jkRT; + } + return jkNone; +} + +int inputDevIndex(const char *node) { + const char *str = strstr(node, "/event"); + if (str) + return atoi(str + 6); + return -1; +} + +void inputDevAdd(const char *node, udev_device *device) { + int index = inputDevIndex(node); + if (index != -1) { + InputDevice &item = inputDevices[index]; + item.fd = open(node, O_RDONLY | O_NONBLOCK); + ioctl(item.fd, EVIOCGRAB, 1); + + item.joyIndex = -1; + if (udev_device_get_property_value(device, "ID_INPUT_JOYSTICK")) { + + // TODO get index from /dev/input/js[N] + + for (int i = 0; i < 4; i++) { // up to 4 gamepads + bool found = true; + for (int j = 0; j < MAX_INPUT_DEVICES; j++) { + if (inputDevices[j].joyIndex == i) { + found = false; + break; + } + } + + if (found) { + item.joyIndex = i; + break; + } + } + } + + //LOG("input: add %s (%d)\n", node, item.joyIndex); + } +} + +void inputDevRemove(const char *node) { + int index = inputDevIndex(node); + if (index != -1 && inputDevices[index].fd != -1) { + close(inputDevices[index].fd); + //LOG("input: remove %s\n", node); + } +} + +bool inputInit() { + joyL = joyR = vec2(0); + + for (int i = 0; i < MAX_INPUT_DEVICES; i++) { + inputDevices[i].fd = -1; + inputDevices[i].joyIndex = -1; + } + + udevObj = udev_new(); + if (!udevObj) + return false; + + udevMon = udev_monitor_new_from_netlink(udevObj, "udev"); + udev_monitor_filter_add_match_subsystem_devtype(udevMon, "input", NULL); + udev_monitor_enable_receiving(udevMon); + udevMon_fd = udev_monitor_get_fd(udevMon); + + udev_enumerate *e = udev_enumerate_new(udevObj); + udev_enumerate_add_match_subsystem(e, "input"); + udev_enumerate_scan_devices(e); + udev_list_entry *devices = udev_enumerate_get_list_entry(e); + + udev_list_entry *entry; + udev_list_entry_foreach(entry, devices) { + const char *path, *node; + udev_device *device; + + path = udev_list_entry_get_name(entry); + device = udev_device_new_from_syspath(udevObj, path); + node = udev_device_get_devnode(device); + + if (node) + inputDevAdd(node, device); + } + udev_enumerate_unref(e); + + return true; +} + +void inputFree() { + for (int i = 0; i < MAX_INPUT_DEVICES; i++) + if (inputDevices[i].fd != -1) + close(inputDevices[i].fd); + udev_monitor_unref(udevMon); + udev_unref(udevObj); +} + +#define JOY_DEAD_ZONE_STICK 8192 + +float joyAxisValue(int value) { + if (value > -JOY_DEAD_ZONE_STICK && value < JOY_DEAD_ZONE_STICK) + return 0.0f; + return value / 32767.0f; +} + +float joyTrigger(int value) { + return min(1.0f, value / 255.0f); +} + +vec2 joyDir(const vec2 &value) { + float dist = min(1.0f, value.length()); + return value.normal() * dist; +} + +void inputUpdate() { +// get input events + input_event events[16]; + + for (int i = 0; i < MAX_INPUT_DEVICES; i++) { + if (inputDevices[i].fd == -1) continue; + int rb = read(inputDevices[i].fd, events, sizeof(events)); + + int joyIndex = inputDevices[i].joyIndex; + + input_event *e = events; + while (rb > 0) { + switch (e->type) { + case EV_KEY : { + InputKey key = codeToInputKey(e->code); + if (key != ikNone) { + if (key == ikMouseL || key == ikMouseR || key == ikMouseM) + Input::setPos(key, Input::mouse.pos); + Input::setDown(key, e->value != 0); + } else { + if (joyIndex == -1) + break; + JoyKey key = codeToJoyKey(e->code); + Input::setJoyDown(joyIndex, key, e->value != 0); + } + break; + } + case EV_REL : { + vec2 delta(0); + delta[e->code] = float(e->value); + Input::setPos(ikMouseL, Input::mouse.pos + delta); + break; + } + case EV_ABS : { + if (joyIndex == -1) + break; + + switch (e->code) { + // Left stick + case ABS_X : joyL.x = joyAxisValue(e->value); break; + case ABS_Y : joyL.y = joyAxisValue(e->value); break; + // Right stick + case ABS_RX : joyR.x = joyAxisValue(e->value); break; + case ABS_RY : joyR.y = joyAxisValue(e->value); break; + // Left trigger + case ABS_Z : Input::setJoyPos(joyIndex, jkLT, joyTrigger(e->value)); break; + // Right trigger + case ABS_RZ : Input::setJoyPos(joyIndex, jkRT, joyTrigger(e->value)); break; + // D-PAD + case ABS_HAT0X : + case ABS_THROTTLE : + Input::setJoyDown(joyIndex, jkLeft, e->value < 0); + Input::setJoyDown(joyIndex, jkRight, e->value > 0); + break; + case ABS_HAT0Y : + case ABS_RUDDER : + Input::setJoyDown(joyIndex, jkUp, e->value < 0); + Input::setJoyDown(joyIndex, jkDown, e->value > 0); + break; + } + + Input::setJoyPos(joyIndex, jkL, joyDir(joyL)); + Input::setJoyPos(joyIndex, jkR, joyDir(joyR)); + } + } + //LOG("input: type = %d, code = %d, value = %d\n", int(e->type), int(e->code), int(e->value)); + e++; + rb -= sizeof(events[0]); + } + } + +// monitoring plug and unplug input devices + fd_set fds; + FD_ZERO(&fds); + FD_SET(udevMon_fd, &fds); + + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + + if (select(udevMon_fd + 1, &fds, NULL, NULL, &tv) > 0 && FD_ISSET(udevMon_fd, &fds)) { + udev_device *device = udev_monitor_receive_device(udevMon); + if (device) { + const char *node = udev_device_get_devnode(device); + if (node) { + const char *action = udev_device_get_action(device); + if (!strcmp(action, "add")) + inputDevAdd(node, device); + if (!strcmp(action, "remove")) + inputDevRemove(node); + } + udev_device_unref(device); + } else + LOG("! input: receive_device\n"); + } +} + +char Stream::cacheDir[255]; +char Stream::contentDir[255]; + +int main(int argc, char **argv) { +LOG("start\n"); + if (!eglInit()) { + LOG("! can't initialize EGL context\n"); + return -1; + } + + Core::width = fb.width; + Core::height = fb.height; + + Stream::contentDir[0] = Stream::cacheDir[0] = 0; + + strcpy(Stream::contentDir, argv[0]); + int i = strlen(Stream::contentDir); + while (--i >= 0) { + if (Stream::contentDir[i] == '/') { + Stream::contentDir[i + 1] = 0; + break; + } + i--; + } + + strcpy(Stream::cacheDir, Stream::contentDir); + strcat(Stream::cacheDir, "cache/"); + + struct stat st = {0}; + if (stat(Stream::cacheDir, &st) == -1 && mkdir(Stream::cacheDir, 0777) == -1) + Stream::cacheDir[0] = 0; + + LOG("cache dir %s\n", Stream::cacheDir); + + timeval t; + gettimeofday(&t, NULL); + startTime = t.tv_sec; + + Game::init(); + inputInit(); + sndInit(); + + while (!Core::isQuit) { + inputUpdate(); + + if (Game::update()) { + Game::render(); + Core::waitVBlank(); + eglSwapBuffers(display, surface); + } else + usleep(9000); + }; + + inputFree(); + + //sndFree(); + //Game::deinit(); + + eglFree(); + + return 0; +} diff --git a/src/shaders/shader.glsl b/src/shaders/shader.glsl index e52ff72..e87a220 100644 --- a/src/shaders/shader.glsl +++ b/src/shaders/shader.glsl @@ -18,6 +18,10 @@ R"====( varying vec4 vTexCoord; // xy - atlas coords, zw - trapezoidal correction +#ifdef OPT_VLIGHTVEC + varying vec3 vLightVec; +#endif + #ifdef OPT_CAUSTICS uniform vec4 uRoomSize; // xy - minXZ, zw - maxXZ #endif @@ -182,6 +186,10 @@ uniform vec4 uFogParams; vec3 lv2 = (uLightPos[2].xyz - coord) * uLightColor[2].w; vec3 lv3 = (uLightPos[3].xyz - coord) * uLightColor[3].w; + #ifdef OPT_VLIGHTVEC + vLightVec = lv0; + #endif + vec4 lum, att; #ifdef TYPE_ENTITY lum.x = dot(vNormal.xyz, normalize(lv0)); @@ -247,7 +255,9 @@ uniform vec4 uFogParams; void _uv(vec3 coord) { vTexCoord = aTexCoord; #if defined(PASS_COMPOSE) && !defined(TYPE_SPRITE) - vTexCoord.xy *= vTexCoord.zw; + #ifdef OPT_TRAPEZOID + vTexCoord.xy *= vTexCoord.zw; + #endif #endif } @@ -378,6 +388,7 @@ uniform vec4 uFogParams; discard; #endif + vec2 uv = vTexCoord.xy; vec4 color; #ifdef TYPE_MIRROR #ifdef PASS_COMPOSE @@ -386,10 +397,11 @@ uniform vec4 uFogParams; #endif #else #if defined(PASS_COMPOSE) && !defined(TYPE_SPRITE) - color = texture2D(sDiffuse, vTexCoord.xy / vTexCoord.zw); - #else - color = texture2D(sDiffuse, vTexCoord.xy); + #ifdef OPT_TRAPEZOID + uv /= vTexCoord.zw; + #endif #endif + color = texture2D(sDiffuse, uv); #endif #ifdef ALPHA_TEST @@ -416,8 +428,12 @@ uniform vec4 uFogParams; #endif #ifdef PASS_COMPOSE - vec3 lightVec = (uLightPos[0].xyz - vCoord) * uLightColor[0].w; - vec3 normal = normalize(vNormal.xyz); + + #ifndef OPT_VLIGHTVEC + vec3 vLightVec = (uLightPos[0].xyz - vCoord) * uLightColor[0].w; + #endif + + vec3 normal = normalize(vNormal.xyz); #ifdef TYPE_ENTITY float rSpecular = uMaterial.z + 0.03; @@ -427,7 +443,7 @@ uniform vec4 uFogParams; vec3 light = uLightColor[1].xyz * vLight.y + uLightColor[2].xyz * vLight.z + uLightColor[3].xyz * vLight.w; #if defined(TYPE_ENTITY) || defined(TYPE_ROOM) - float rShadow = getShadow(lightVec, normal); + float rShadow = getShadow(vLightVec, normal); #endif #ifdef TYPE_ENTITY @@ -458,7 +474,7 @@ uniform vec4 uFogParams; color.xyz *= light; #ifdef TYPE_ENTITY - color.xyz += calcSpecular(normal, vViewVec.xyz, lightVec, uLightColor[0], rSpecular); + color.xyz += calcSpecular(normal, vViewVec.xyz, vLightVec, uLightColor[0], rSpecular); #endif #ifdef UNDERWATER diff --git a/src/utils.h b/src/utils.h index 497b41c..3e7b02f 100644 --- a/src/utils.h +++ b/src/utils.h @@ -6,7 +6,7 @@ #include #ifdef _DEBUG - #ifdef _OS_LINUX + #if defined(_OS_LINUX) || defined(_OS_RPI) || defined(_OS_CLOVER) #define debugBreak() raise(SIGTRAP); #else #define debugBreak() _asm { int 3 }